feat(desktop): optimize cold development startup (#42722)

This commit is contained in:
Luke Parker 2026-08-20 17:40:50 +10:00 committed by GitHub
parent e2d9376614
commit 2a7d0729d0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
113 changed files with 2479 additions and 2656 deletions

2219
bun.lock

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,7 @@
"dev:stats": "bun sst shell --stage=production -- bun run --cwd packages/stats/app dev",
"dev:www": "bun run --cwd packages/www dev",
"dev:storybook": "bun --cwd packages/storybook storybook",
"bench:devex": "bun run --cwd packages/app test:bench:devex",
"lint": "oxlint",
"lint:effect-patterns": "ast-grep scan -c script/ast-grep/sgconfig.yml packages/util/src packages/core/src packages/server/src packages/protocol/src packages/cli/src",
"test:lint-rules": "ast-grep test -c script/ast-grep/sgconfig.yml",

View file

@ -2,12 +2,34 @@
The app's high-volume performance diagnostics live under `packages/app/e2e/performance` and are excluded from normal local and CI Playwright discovery. The benchmark config builds the app and serves the production bundle before running scenarios serially.
The `devex` category is the explicit exception to the production-build rule. It measures development commands from submission through a user-visible ready state and has its own Playwright configuration.
Run the suite explicitly from `packages/app`:
```sh
bun run test:bench
```
Run the desktop development startup benchmark from the repository root:
```sh
bun run bench:devex
```
It runs five serial samples of the exact `bun dev:desktop` command. Each sample uses a fresh desktop profile, database, service configuration, service registration, and service process; the desktop selects an isolated ephemeral loopback endpoint. It removes desktop build output and the desktop Vite cache before every run; dependencies, Bun's package cache, and Electron remain installed. The harness stops only that sample's service; it does not stop or change the elected global OpenCode service. The measured endpoint is a visible Home page whose empty-state controls pass Playwright actionability checks. The command's Electron installation check remains inside the measured interval.
Set `DESKTOP_STARTUP_RUNS` only for focused diagnostics:
```sh
DESKTOP_STARTUP_RUNS=1 bun run bench:devex
```
Set `OPENCODE_PERFORMANCE_TRACE_DIR` to capture the renderer's CDP trace from attachment through actionable Home:
```sh
DESKTOP_STARTUP_RUNS=1 OPENCODE_PERFORMANCE_TRACE_DIR=/tmp/opencode-desktop-traces bun run bench:devex
```
PowerShell:
```powershell

View file

@ -18,9 +18,9 @@ const categories = [
"disabled-by-default-v8.cpu_profiler",
]
export async function startChromeTrace(page: Page, name: string) {
export async function startChromeTrace(page: Page, name: string): Promise<undefined | (() => Promise<string>)> {
const directory = process.env.OPENCODE_PERFORMANCE_TRACE_DIR
if (!directory) return
if (!directory) return undefined
const selectors = process.env.OPENCODE_PERFORMANCE_SELECTOR_TRACE === "1"
const file = await prepareChromeTrace(directory, name, selectors)

View file

@ -0,0 +1,26 @@
import { benchmark } from "../benchmark"
import {
desktopBenchmarkContext,
runDesktopStartup,
summarizeDesktopStartup,
type DesktopStartupSample,
} from "./desktop-startup"
benchmark.describe("devex: desktop startup", () => {
benchmark("opens a cold desktop on Home", async ({ report }, testInfo) => {
benchmark.setTimeout(15 * 60_000)
const runs = Number(process.env.DESKTOP_STARTUP_RUNS ?? 5)
if (!Number.isSafeInteger(runs) || runs < 1) throw new Error("DESKTOP_STARTUP_RUNS must be a positive integer")
const samples: DesktopStartupSample[] = []
const context = await desktopBenchmarkContext(runs)
for (let run = 1; run <= runs; run++) {
const sample = await runDesktopStartup(run, testInfo).catch((error) => {
report(samples.length ? { samples, summary: summarizeDesktopStartup(samples) } : { samples }, context)
throw error
})
samples.push(sample)
}
report({ samples, summary: summarizeDesktopStartup(samples) }, context)
})
})

View file

@ -0,0 +1,526 @@
import { Service } from "@opencode-ai/client/service"
import { chromium, expect, type Browser, type Page, type TestInfo } from "@playwright/test"
import { spawn, spawnSync, type ChildProcess } from "node:child_process"
import { mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises"
import { tmpdir } from "node:os"
import { join, resolve } from "node:path"
import { startChromeTrace } from "../chrome-trace"
const repository = resolve(import.meta.dirname, "../../../../..")
const milestones = [
"bunRootScript",
"bunDesktopScript",
"desktopPrepared",
"mainBundleReady",
"preloadBundleReady",
"rendererDevServerReady",
"electronSpawnStarted",
"debugEndpointReady",
"electronStarted",
"serviceEnsureStarted",
"serviceSpawnRequested",
"serviceReady",
"backgroundLoadingReady",
"rendererViteConnected",
"rendererInitializationStarted",
"rendererInitializationReady",
"windowVisible",
"homeReady",
] as const
const phases = [
"desktopPreparation",
"viteMainBundle",
"vitePreloadBundle",
"rendererServerStartup",
"electronStartup",
"serviceSpawnWait",
"serviceProcessStartup",
"rendererStartup",
"visibleWindowToHome",
] as const
type Milestone = (typeof milestones)[number]
type Phase = (typeof phases)[number]
type ServiceInfo = { id: string; version: string; url: string; pid: number }
export type DesktopStartupSample = {
run: number
commandToHomeReadyMs: number
milestonesMs: Record<Milestone, number>
phasesMs: Record<Phase, number>
service: Omit<ServiceInfo, "id">
}
export async function runDesktopStartup(run: number, testInfo: TestInfo) {
const profile = await createColdProfile()
const desktop = await Promise.resolve()
.then(() => startDesktop(profile))
.catch(async (error) => {
await rm(profile.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })
throw error
})
try {
const page = await desktop.open()
const stopTrace = await startChromeTrace(page, `desktop-startup-${run}`)
try {
await startThemeObservation(page)
await waitForHome(page, desktop.mark)
await requireStableTheme(page)
return await desktop.result(run)
} finally {
await stopTrace?.()
}
} finally {
await desktop.close(testInfo, run)
}
}
export async function desktopBenchmarkContext(runs: number) {
const pkg = JSON.parse(await readFile(join(repository, "packages/desktop/package.json"), "utf8"))
const revision = spawnSync("git", ["rev-parse", "HEAD"], { cwd: repository })
if (revision.status !== 0) throw new Error("Failed to read the benchmark Git revision")
const status = spawnSync("git", ["status", "--porcelain"], { cwd: repository })
if (status.status !== 0) throw new Error("Failed to read the benchmark Git status")
const bun = spawnSync("bun", ["--version"], { cwd: repository })
if (bun.status !== 0) throw new Error("Failed to read the benchmark Bun version")
return {
arch: process.arch,
command: "bun dev:desktop",
runs,
profile: "fresh",
service: "isolated-cold",
install: "complete",
viteCache: "cold",
electronInstall: "present",
bunVersion: bun.stdout.toString().trim(),
electronVersion: pkg.devDependencies.electron,
electronViteVersionRange: pkg.devDependencies["electron-vite"],
gitCommit: revision.stdout.toString().trim(),
gitDirty: status.stdout.length > 0,
trace: Boolean(process.env.OPENCODE_PERFORMANCE_TRACE_DIR),
}
}
export function summarizeDesktopStartup(samples: DesktopStartupSample[]) {
return {
commandToHomeReadyMs: statistics(samples.map((sample) => sample.commandToHomeReadyMs)),
milestonesMs: Object.fromEntries(
milestones.map((name) => [name, statistics(samples.map((sample) => sample.milestonesMs[name]))]),
),
phasesMs: Object.fromEntries(
phases.map((name) => [name, statistics(samples.map((sample) => sample.phasesMs[name]))]),
),
}
}
export function milestoneForLine(line: string): Milestone | undefined {
const text = stripAnsi(line)
return milestonePatterns.find((item) => text.includes(item.text))?.name
}
const milestonePatterns: ReadonlyArray<{ name: Milestone; text: string }> = [
{ name: "bunRootScript", text: "$ bun --cwd packages/desktop dev" },
{ name: "bunDesktopScript", text: "$ bun ./scripts/dev.ts" },
{ name: "desktopPrepared", text: "Copied dev icons from" },
{ name: "mainBundleReady", text: "electron main process built successfully" },
{ name: "preloadBundleReady", text: "electron preload scripts built successfully" },
{ name: "rendererDevServerReady", text: "dev server running for the electron renderer process at:" },
{ name: "electronSpawnStarted", text: "starting electron app..." },
{ name: "debugEndpointReady", text: "DevTools listening on ws://" },
{ name: "electronStarted", text: "app starting" },
{ name: "serviceEnsureStarted", text: "starting v2 background service" },
{ name: "serviceSpawnRequested", text: "v2 CLI background service starting" },
{ name: "serviceReady", text: "v2 CLI background service ready" },
{ name: "backgroundLoadingReady", text: "loading task finished" },
{ name: "rendererViteConnected", text: "[vite] connected." },
{ name: "rendererInitializationStarted", text: "awaiting server ready" },
{ name: "rendererInitializationReady", text: "server ready" },
{ name: "windowVisible", text: "main window visible" },
]
async function createColdProfile() {
await Promise.all(
["packages/desktop/node_modules/.vite", "packages/desktop/out"].map((path) =>
rm(join(repository, path), { recursive: true, force: true }),
),
)
const root = await mkdtemp(join(tmpdir(), "opencode-desktop-startup-"))
return initializeColdProfile(root).catch(async (error) => {
await rm(root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 })
throw error
})
}
async function initializeColdProfile(root: string) {
await Promise.all(
["data", "config", "cache", "state", "desktop", "session", "home"].map((dir) =>
mkdir(join(root, dir), { recursive: true }),
),
)
await Promise.all([
writeFile(
join(root, "desktop", "opencode.settings"),
JSON.stringify({ firstLaunchOnboardingComplete: true }),
),
writeFile(join(root, "desktop", "opencode.global.dat"), JSON.stringify({ language: '{"locale":"en"}' })),
])
const registration = join(root, "desktop", "opencode", "service-local.json")
await Service.stop({ file: registration })
return { root, registration }
}
function startDesktop(profile: Awaited<ReturnType<typeof createColdProfile>>) {
const started = performance.now()
const child = spawn("bun", ["dev:desktop"], {
cwd: repository,
detached: process.platform !== "win32",
env: {
...process.env,
OPENCODE_CONFIG_DIR: join(profile.root, "config"),
OPENCODE_DB: join(profile.root, "data", "opencode.db"),
OPENCODE_TEST_HOME: join(profile.root, "home"),
OPENCODE_TEST_ONBOARDING: "0",
OPENCODE_DESKTOP_TEST_ROOT: profile.root,
OPENCODE_DESKTOP_REMOTE_DEBUGGING_PORT: "0",
OPENCODE_DESKTOP_DISABLE_PROTOCOL_REGISTRATION: "1",
},
stdio: ["ignore", "pipe", "pipe"],
})
if (!child.pid || !child.stdout || !child.stderr) throw new Error("Failed to start the desktop command")
const exited = childExit(child)
const observed: Partial<Record<Milestone, number>> = {}
const endpoint = Promise.withResolvers<string>()
const pageErrors: string[] = []
let browser: Browser | undefined
let service: ServiceInfo | undefined
const mark = (name: Milestone) => {
observed[name] ??= elapsed(started)
}
const record = (line: string) => {
const milestone = milestoneForLine(line)
if (milestone) mark(milestone)
const match = stripAnsi(line).match(/DevTools listening on (ws:\/\/\S+)/)
if (match?.[1]) endpoint.resolve(match[1])
}
const stdout = observeOutput(child.stdout, record)
const stderr = observeOutput(child.stderr, record)
return {
mark,
async open() {
const url = await Promise.race([
endpoint.promise,
exited.then((code) => {
throw new Error(`Desktop command exited with code ${code} before opening its debug endpoint`)
}),
sleep(120_000).then(() => {
throw new Error("Timed out waiting for the desktop debug endpoint")
}),
])
browser = await chromium.connectOverCDP(url, { timeout: 120_000 })
const context = browser.contexts()[0]
if (!context) throw new Error("Electron did not expose a browser context")
await expect.poll(() => context.pages().length, { timeout: 120_000 }).toBeGreaterThan(0)
const page = context.pages()[0]
if (!page) throw new Error("Electron did not expose a renderer page")
page.on("pageerror", (error) => pageErrors.push(error.stack ?? error.message))
return page
},
async result(run: number): Promise<DesktopStartupSample> {
if (pageErrors.length) throw new Error(`Desktop renderer reported errors:\n\n${pageErrors.join("\n\n")}`)
service = await readService(profile)
const milestonesMs = requireMilestones(observed)
return {
run,
commandToHomeReadyMs: milestonesMs.homeReady,
milestonesMs,
phasesMs: calculatePhases(milestonesMs),
service: {
version: service.version,
url: service.url,
pid: service.pid,
},
}
},
async close(testInfo: TestInfo, run: number) {
const errors: unknown[] = []
await browser?.close().catch(() => undefined)
await stopProcessTree(child, exited).catch((error) => {
errors.push(error)
child.stdout?.destroy()
child.stderr?.destroy()
})
const [stdoutText, stderrText] = await Promise.all([stdout, stderr]).catch((error) => {
errors.push(error)
return ["", ""]
})
await Promise.all([
testInfo.attach(`desktop-startup-${run}-stdout`, { body: stdoutText, contentType: "text/plain" }),
testInfo.attach(`desktop-startup-${run}-stderr`, { body: stderrText, contentType: "text/plain" }),
pageErrors.length
? testInfo.attach(`desktop-startup-${run}-page-errors`, {
body: pageErrors.join("\n\n"),
contentType: "text/plain",
})
: Promise.resolve(),
]).catch((error) => errors.push(error))
await Service.stop({ file: profile.registration }).catch((error) => errors.push(error))
if (service && processAlive(service.pid))
errors.push(new Error(`Desktop service process ${service.pid} did not stop`))
await rm(profile.root, { recursive: true, force: true, maxRetries: 5, retryDelay: 100 }).catch((error) =>
errors.push(error),
)
if (errors.length) throw new AggregateError(errors, "Desktop benchmark cleanup failed")
},
}
}
async function waitForHome(page: Page, mark: (name: Milestone) => void) {
await expect.poll(() => page.evaluate(() => document.visibilityState), { timeout: 120_000 }).toBe("visible")
const projects = page.getByRole("complementary", { name: "Projects", exact: true })
const sessions = page.getByRole("region", { name: "Recent sessions", exact: true })
const search = page.getByRole("textbox", { name: "Search sessions", exact: true })
const addProject = projects.locator('button[data-action="home-add-project-row"]')
await expect(projects).toBeVisible({ timeout: 120_000 })
await expect(sessions).toBeVisible()
await expect(search).toBeEditable()
await expect(sessions.getByText("Nothing here yet", { exact: true })).toBeVisible()
await expect(addProject).toHaveCount(1)
await addProject.click({ trial: true })
mark("homeReady")
}
type ThemeWindow = Window & {
__OPENCODE_THEME_STATES__?: string[]
__OPENCODE_THEME_OBSERVER__?: MutationObserver
}
async function startThemeObservation(page: Page) {
await page.addInitScript(installThemeObservation)
await page.evaluate(installThemeObservation)
}
async function requireStableTheme(page: Page) {
const states = await page.evaluate(() => {
const target = window as ThemeWindow
target.__OPENCODE_THEME_OBSERVER__?.disconnect()
return target.__OPENCODE_THEME_STATES__ ?? []
})
if (states.length !== 1) throw new Error(`Desktop theme changed during startup: ${states.join(" -> ")}`)
}
function installThemeObservation() {
const target = window as ThemeWindow
const observeRoot = () => {
const root = document.documentElement
if (!root) return false
const state = () => {
const theme = root.dataset.theme
const scheme = root.dataset.colorScheme
return theme && scheme ? `${theme}:${scheme}` : undefined
}
const initial = state()
target.__OPENCODE_THEME_STATES__ = initial ? [initial] : []
target.__OPENCODE_THEME_OBSERVER__ = new MutationObserver(() => {
const next = state()
if (!next) return
if (target.__OPENCODE_THEME_STATES__?.at(-1) !== next) target.__OPENCODE_THEME_STATES__?.push(next)
})
target.__OPENCODE_THEME_OBSERVER__.observe(root, {
attributes: true,
attributeFilter: ["data-theme", "data-color-scheme"],
})
return true
}
if (observeRoot()) return
const documentObserver = new MutationObserver(() => {
if (!observeRoot()) return
documentObserver.disconnect()
})
target.__OPENCODE_THEME_OBSERVER__ = documentObserver
documentObserver.observe(document, { childList: true })
}
async function observeOutput(stream: NodeJS.ReadableStream, record: (line: string) => void) {
const decoder = new TextDecoder()
const output: string[] = []
let pending = ""
for await (const chunk of stream) {
const text = typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true })
output.push(text)
pending += text
const lines = pending.split(/\r?\n/)
pending = lines.pop() ?? ""
lines.forEach(record)
}
const final = decoder.decode()
output.push(final)
pending += final
if (pending) record(pending)
return output.join("")
}
async function readService(profile: Awaited<ReturnType<typeof createColdProfile>>) {
const value: unknown = JSON.parse(await readFile(profile.registration, "utf8"))
if (!isServiceInfo(value)) throw new Error("Desktop service registration is invalid")
const url = new URL(value.url)
const port = Number(url.port)
if (url.hostname !== "127.0.0.1" || !Number.isInteger(port) || port <= 0)
throw new Error(`Desktop service used unexpected endpoint ${value.url}`)
if (!value.version.startsWith("2.0.0-local-"))
throw new Error(`Desktop service used unexpected version ${value.version}`)
return value
}
function isServiceInfo(value: unknown): value is ServiceInfo {
return (
typeof value === "object" &&
value !== null &&
"id" in value &&
typeof value.id === "string" &&
"version" in value &&
typeof value.version === "string" &&
"url" in value &&
typeof value.url === "string" &&
"pid" in value &&
typeof value.pid === "number"
)
}
function requireMilestones(observed: Partial<Record<Milestone, number>>) {
const get = (name: Milestone) => {
const value = observed[name]
if (value === undefined) throw new Error(`Desktop startup did not report milestone: ${name}`)
return round(value)
}
return {
bunRootScript: get("bunRootScript"),
bunDesktopScript: get("bunDesktopScript"),
desktopPrepared: get("desktopPrepared"),
mainBundleReady: get("mainBundleReady"),
preloadBundleReady: get("preloadBundleReady"),
rendererDevServerReady: get("rendererDevServerReady"),
electronSpawnStarted: get("electronSpawnStarted"),
debugEndpointReady: get("debugEndpointReady"),
electronStarted: get("electronStarted"),
serviceEnsureStarted: get("serviceEnsureStarted"),
serviceSpawnRequested: get("serviceSpawnRequested"),
serviceReady: get("serviceReady"),
backgroundLoadingReady: get("backgroundLoadingReady"),
rendererViteConnected: get("rendererViteConnected"),
rendererInitializationStarted: get("rendererInitializationStarted"),
rendererInitializationReady: get("rendererInitializationReady"),
windowVisible: get("windowVisible"),
homeReady: get("homeReady"),
}
}
function calculatePhases(value: Record<Milestone, number>): Record<Phase, number> {
return {
desktopPreparation: value.desktopPrepared,
viteMainBundle: round(value.mainBundleReady - value.desktopPrepared),
vitePreloadBundle: round(value.preloadBundleReady - value.mainBundleReady),
rendererServerStartup: round(value.rendererDevServerReady - value.preloadBundleReady),
electronStartup: round(value.electronStarted - value.electronSpawnStarted),
serviceSpawnWait: round(value.serviceSpawnRequested - value.serviceEnsureStarted),
serviceProcessStartup: round(value.serviceReady - value.serviceSpawnRequested),
rendererStartup: round(value.homeReady - value.rendererViteConnected),
visibleWindowToHome: round(value.homeReady - value.windowVisible),
}
}
function statistics(values: number[]) {
if (!values.length) throw new Error("Cannot summarize an empty benchmark")
const sorted = values.toSorted((left, right) => left - right)
const median = medianOf(sorted)
return {
min: round(sorted[0]),
median: round(median),
max: round(sorted.at(-1)!),
medianAbsoluteDeviation: round(medianOf(sorted.map((value) => Math.abs(value - median)).toSorted((a, b) => a - b))),
}
}
function medianOf(sorted: number[]) {
const middle = Math.floor(sorted.length / 2)
if (sorted.length % 2) return sorted[middle]
return (sorted[middle - 1] + sorted[middle]) / 2
}
async function stopProcessTree(child: ChildProcess, exited: Promise<number | null>) {
if (!child.pid) throw new Error("Desktop command has no process ID")
if (process.platform !== "win32") return stopProcessGroup(child.pid, exited)
if (child.exitCode !== null || (await exitsWithin(child, exited, 2_000))) return
const kill = spawn("taskkill.exe", ["/PID", String(child.pid), "/T", "/F"], {
stdio: "ignore",
})
await childExit(kill)
if (await exitsWithin(child, exited, 10_000)) return
if (!(await exitsWithin(child, exited, 5_000))) throw new Error(`Desktop command process ${child.pid} did not stop`)
}
async function stopProcessGroup(pid: number, exited: Promise<number | null>) {
await Promise.race([exited, sleep(2_000)])
if (!processGroupAlive(pid)) return
process.kill(-pid, "SIGTERM")
if (await processGroupStopsWithin(pid, 10_000)) return
process.kill(-pid, "SIGKILL")
if (!(await processGroupStopsWithin(pid, 5_000))) throw new Error(`Desktop command process group ${pid} did not stop`)
}
async function processGroupStopsWithin(pid: number, timeout: number) {
const deadline = Date.now() + timeout
while (Date.now() < deadline) {
if (!processGroupAlive(pid)) return true
await sleep(50)
}
return !processGroupAlive(pid)
}
function processGroupAlive(pid: number) {
try {
process.kill(-pid, 0)
return true
} catch {
return false
}
}
async function exitsWithin(child: ChildProcess, exited: Promise<number | null>, timeout: number) {
if (child.exitCode !== null) return true
const result = await Promise.race([exited.then(() => true), sleep(timeout).then(() => false)])
return result
}
function childExit(child: ChildProcess) {
return new Promise<number | null>((resolve, reject) => {
child.once("error", reject)
child.once("exit", (code) => resolve(code))
})
}
function sleep(milliseconds: number) {
return new Promise<void>((resolve) => setTimeout(resolve, milliseconds))
}
function processAlive(pid: number) {
try {
process.kill(pid, 0)
return true
} catch {
return false
}
}
function stripAnsi(value: string) {
return value.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "")
}
function elapsed(started: number) {
return round(performance.now() - started)
}
function round(value: number) {
return Math.round(value * 100) / 100
}

View file

@ -0,0 +1,17 @@
import { defineConfig } from "@playwright/test"
process.env.OPENCODE_PERFORMANCE_RUN_ID ??= `${new Date().toISOString().replace(/[:.]/g, "-")}-${process.pid}`
export default defineConfig({
testDir: ".",
testMatch: "desktop-startup-benchmark.spec.ts",
outputDir: "../../test-results/performance-devex",
timeout: 15 * 60_000,
expect: {
timeout: 120_000,
},
fullyParallel: false,
workers: 1,
reporter: [["html", { outputFolder: "../../playwright-report/performance-devex", open: "never" }], ["line"]],
projects: [{ name: "desktop" }],
})

View file

@ -7,7 +7,7 @@ process.env.OPENCODE_PERFORMANCE_RUN_ID ??= `${new Date().toISOString().replace(
export default {
...config,
testDir: ".",
testIgnore: "unit/**",
testIgnore: ["unit/**", "devex/**"],
outputDir: "../test-results/performance",
fullyParallel: false,
workers: 1,

View file

@ -0,0 +1,88 @@
import { describe, expect, test } from "bun:test"
import { inlineThemePreload } from "../../../vite.js"
import { milestoneForLine, summarizeDesktopStartup, type DesktopStartupSample } from "../devex/desktop-startup"
describe("desktop startup benchmark", () => {
test.each(["/oc-theme-preload.js", "./oc-theme-preload.js"])("inlines %s before the renderer runs", (path) => {
const html = inlineThemePreload(`<script id="oc-theme-preload-script" src="${path}"></script>`)
expect(html).not.toContain(" src=")
expect(html).toContain("opencode-color-scheme")
})
test("recognizes startup milestones in colored output", () => {
const cases = [
["bunRootScript", "$ bun --cwd packages/desktop dev"],
["bunDesktopScript", "$ bun ./scripts/dev.ts"],
["desktopPrepared", "Copied dev icons from"],
["mainBundleReady", "electron main process built successfully"],
["preloadBundleReady", "electron preload scripts built successfully"],
["rendererDevServerReady", "dev server running for the electron renderer process at:"],
["electronSpawnStarted", "starting electron app..."],
["debugEndpointReady", "DevTools listening on ws://"],
["electronStarted", "app starting"],
["serviceEnsureStarted", "starting v2 background service"],
["serviceSpawnRequested", "v2 CLI background service starting"],
["serviceReady", "v2 CLI background service ready"],
["backgroundLoadingReady", "loading task finished"],
["rendererViteConnected", "[vite] connected."],
["rendererInitializationStarted", "awaiting server ready"],
["rendererInitializationReady", "server ready"],
["windowVisible", "main window visible"],
] as const
cases.forEach(([milestone, line]) => {
expect(milestoneForLine(`\u001b[32m${line}\u001b[39m`)).toBe(milestone)
})
expect(milestoneForLine("12:30:00.000 v2 CLI background service ready {")).toBe("serviceReady")
expect(milestoneForLine("unrelated output")).toBeUndefined()
})
test("keeps raw samples and reports median absolute deviation", () => {
const samples = [24, 20, 22, 28, 26].map((commandToHomeReadyMs, index) => sample(index + 1, commandToHomeReadyMs))
expect(summarizeDesktopStartup(samples).commandToHomeReadyMs).toEqual({
min: 20,
median: 24,
max: 28,
medianAbsoluteDeviation: 2,
})
})
})
function sample(run: number, commandToHomeReadyMs: number): DesktopStartupSample {
const milestonesMs = {
bunRootScript: 1,
bunDesktopScript: 2,
desktopPrepared: 3,
mainBundleReady: 4,
preloadBundleReady: 5,
rendererDevServerReady: 6,
electronSpawnStarted: 7,
debugEndpointReady: 8,
electronStarted: 9,
serviceEnsureStarted: 10,
serviceSpawnRequested: 11,
serviceReady: 12,
backgroundLoadingReady: 13,
rendererViteConnected: 14,
rendererInitializationStarted: 15,
rendererInitializationReady: 16,
windowVisible: 17,
homeReady: commandToHomeReadyMs,
}
return {
run,
commandToHomeReadyMs,
milestonesMs,
phasesMs: {
desktopPreparation: 3,
viteMainBundle: 1,
vitePreloadBundle: 1,
rendererServerStartup: 1,
electronStartup: 2,
serviceSpawnWait: 1,
serviceProcessStartup: 1,
rendererStartup: commandToHomeReadyMs - 14,
visibleWindowToHome: commandToHomeReadyMs - 17,
},
service: { version: "2.0.0-local-test", url: "http://127.0.0.1:3000", pid: run },
}
}

View file

@ -36,6 +36,9 @@ test("preserves the draft when a populated command menu triggers a built-in", as
await page.goto(`/server/${base64Encode(server)}/session/${sessionID}`)
const composer = page.locator('[data-component="prompt-input-v2"]')
const input = composer.locator('[data-component="prompt-input"]')
await expect.poll(() => input.evaluate((element) => getComputedStyle(element, "::before").content)).toBe(
`"${String.fromCodePoint(0x200b)}"`,
)
await expectAppVisible(composer)
await input.fill("keep me")

View file

@ -19,7 +19,7 @@ test("session settings use the remote server context", async ({ page }) => {
await configureServers(page)
await page.goto(`/server/${base64Encode(serverB)}/session/${sessionB.id}`)
await expect(page.getByText(sessionB.title).first()).toBeVisible()
await expect(page.getByRole("heading", { name: sessionB.title, exact: true })).toBeVisible()
await page.keyboard.press("Control+,")
const dialog = page.locator(".settings-v2-dialog")
@ -61,7 +61,7 @@ test("auto-accept responds for an unfocused server session", async ({ page }) =>
const hrefB = `/server/${base64Encode(serverB)}/session/${sessionB.id}`
await page.goto(`/server/${base64Encode(serverA)}/session/${sessionA.id}`)
await expect(page.getByText(sessionA.title).first()).toBeVisible()
await expect(page.getByRole("heading", { name: sessionA.title, exact: true })).toBeVisible()
await page.keyboard.press("Control+,")
const autoAccept = page.locator(".settings-v2-dialog").locator('[data-action="settings-auto-accept-permissions"]')
await autoAccept.locator('[data-slot="switch-control"]').click()
@ -78,7 +78,7 @@ test("auto-accept responds for an unfocused server session", async ({ page }) =>
await page.locator(`[data-titlebar-tab-slot]:has(a[href="${hrefB}"])`).click()
await expect(page).toHaveURL(new RegExp(`${hrefB.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}$`))
await expect(page.getByText(sessionB.title).first()).toBeVisible()
await expect(page.getByRole("heading", { name: sessionB.title, exact: true })).toBeVisible()
await transport.waitForConnection()
await transport.send({
@ -208,7 +208,7 @@ async function mockServers(page: Page, permissionRequests: string[], permissionR
return json(route, [
{
id: remote ? sessionB.projectID : "project-server-a",
worktree: directory,
canonical: directory,
vcs: "git",
time: { created: 1, updated: 1 },
sandboxes: [],
@ -216,7 +216,7 @@ async function mockServers(page: Page, permissionRequests: string[], permissionR
])
}
if (url.pathname === "/api/project/current")
return json(route, { id: remote ? sessionB.projectID : "project-server-a", directory })
return json(route, { id: remote ? sessionB.projectID : "project-server-a", directory, canonical: directory })
if (url.pathname === "/api/session")
return json(route, { data: sessions.map((session) => currentSession(session)), cursor: {} })
if (url.pathname === "/api/session/active") return json(route, { data: {} })
@ -237,7 +237,7 @@ function session(id: string, directory: string, title: string) {
id,
slug: id,
projectID: `project-${id}`,
directory,
location: { directory },
title,
version: "dev",
time: { created: 1, updated: 1 },

View file

@ -5,6 +5,7 @@
"type": "module",
"exports": {
".": "./src/index.ts",
"./desktop": "./src/desktop.ts",
"./desktop-menu": "./src/desktop-menu.ts",
"./i18n/desktop-native": "./src/i18n/desktop-native.ts",
"./updater": "./src/updater.ts",
@ -28,14 +29,15 @@
"test:e2e:ui": "playwright test --ui",
"test:e2e:report": "playwright show-report e2e/playwright-report",
"test:stability": "bun test ./e2e/performance/unit/visual-stability.test.ts && playwright test --config e2e/performance/timeline-stability/playwright.config.ts",
"test:bench": "bun test ./e2e/performance/unit && playwright test --config e2e/performance/playwright.config.ts"
"test:bench": "bun test ./e2e/performance/unit && playwright test --config e2e/performance/playwright.config.ts",
"test:bench:devex": "bun test ./e2e/performance/unit/desktop-startup.test.ts && playwright test --config e2e/performance/devex/playwright.config.ts"
},
"license": "MIT",
"devDependencies": {
"@happy-dom/global-registrator": "20.0.11",
"@playwright/test": "catalog:",
"@sentry/vite-plugin": "catalog:",
"@tailwindcss/vite": "catalog:",
"@tailwindcss/vite": "4.3.3",
"@tsconfig/bun": "1.0.9",
"@types/bun": "catalog:",
"@types/luxon": "catalog:",
@ -44,9 +46,9 @@
"happy-dom": "20.11.1",
"tw-animate-css": "1.4.0",
"typescript": "catalog:",
"vite": "catalog:",
"vite": "8.2.1",
"vite-plugin-icons-spritesheet": "3.0.1",
"vite-plugin-solid": "catalog:"
"vite-plugin-solid": "2.11.14"
},
"dependencies": {
"@corvu/drawer": "catalog:",
@ -85,6 +87,6 @@
"solid-js": "catalog:",
"solid-list": "catalog:",
"solid-presence": "0.2.0",
"tailwindcss": "catalog:"
"tailwindcss": "4.3.3"
}
}

View file

@ -1,12 +1,10 @@
import "@/index.css"
import * as Sentry from "@sentry/solid"
import { DialogProvider } from "@opencode-ai/ui/context/dialog"
import { FileComponentProvider } from "@opencode-ai/ui/context/file"
import { File } from "@opencode-ai/session-ui/file"
import { Font } from "@opencode-ai/ui/font"
import { ThemeProvider } from "@opencode-ai/ui/theme/context"
import { MetaProvider } from "@solidjs/meta"
import { type BaseRouterProps, Navigate, Route, Router, useParams, useSearchParams } from "@solidjs/router"
import { type BaseRouterProps, Route, Router, useParams } from "@solidjs/router"
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
import {
type Component,
@ -20,30 +18,37 @@ import {
} from "solid-js"
import { Dynamic } from "solid-js/web"
import { CommandProvider, useCommand, type CommandOption } from "@/context/command"
import { CommentsProvider } from "@/context/comments"
import { FileProvider } from "@/context/file"
import { GlobalProvider, useGlobal } from "@/context/global"
import { HighlightsProvider } from "@/context/highlights"
import { LanguageProvider, UiI18nBridge, type Locale, useLanguage } from "@/context/language"
import { LayoutProvider } from "@/context/layout"
import { ModelsProvider } from "@/context/models"
import { usePlatform } from "@/context/platform"
import { PromptProvider } from "@/context/prompt"
import { ServerConnection, ServersProvider } from "@/context/servers"
import { SettingsProvider } from "@/context/settings"
import { TabsProvider, useTabs, type DraftTab } from "@/context/tabs"
import { LocationProvider } from "@/context/location"
import { TabsProvider } from "@/context/tabs"
import { WslServersProvider } from "@/wsl/context"
import { SessionUIProvider } from "@/pages/directory-layout"
import Layout from "@/pages/layout"
import { ErrorPage } from "./pages/error"
import { requireServerKey } from "./utils/session-route"
import { TargetSessionRouteContent } from "@/pages/session"
import { Home } from "@/pages/home"
import { ServerProvider } from "./context/server"
const NewSession = lazy(() => import("@/pages/new-session"))
const File = lazy(() => import("@opencode-ai/session-ui/file").then((module) => ({ default: module.File })))
const loadDraftRoute = () => Promise.all([import("@/pages/draft-route"), File.preload()]).then(([module]) => module)
const loadSessionRoute = () => Promise.all([import("@/pages/session"), File.preload()]).then(([module]) => module)
const DraftRoute = lazy(() => loadDraftRoute().then((module) => ({ default: module.DraftRoute })))
const TargetSessionRouteContent = lazy(() =>
loadSessionRoute().then((module) => ({ default: module.TargetSessionRouteContent })),
)
export function preloadRoute(url: string) {
const pathname = url.split(/[?#]/, 1)[0]
if (pathname === "/new-session") return DraftRoute.preload().then(() => undefined)
if (/^\/server\/[^/]+\/session\/[^/]+$/.test(pathname))
return TargetSessionRouteContent.preload().then(() => undefined)
return Promise.resolve()
}
function TargetServerRoute(props: ParentProps) {
const params = useParams<{ serverKey: string }>()
@ -60,45 +65,6 @@ function TargetServerRoute(props: ParentProps) {
)
}
function DraftRoute() {
const [search] = useSearchParams<{ draftId?: string }>()
const tabs = useTabs()
return (
<Show
when={tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)}
keyed
fallback={tabs.ready() && <Navigate href="/" />}
>
{(draft) => <ResolvedDraftRoute draft={draft} />}
</Show>
)
}
function ResolvedDraftRoute(props: { draft: DraftTab }) {
const global = useGlobal()
const conn = createMemo(() => global.servers.list().find((item) => ServerConnection.key(item) === props.draft.server))
return (
<Show when={`${props.draft.server}\0${props.draft.directory}`} keyed>
<Show when={conn()} keyed>
{(conn) => (
<ServerProvider conn={conn}>
<ModelsProvider directory={props.draft.directory}>
<LocationProvider directory={props.draft.directory}>
<SessionUIProvider directory={props.draft.directory} server={props.draft.server}>
<DraftProviders>
<NewSession draftId={props.draft.draftID} />
</DraftProviders>
</SessionUIProvider>
</LocationProvider>
</ModelsProvider>
</ServerProvider>
)}
</Show>
</Show>
)
}
declare global {
interface Window {
__OPENCODE__?: {
@ -167,22 +133,11 @@ function AppLayout(props: ParentProps) {
)
}
// The draft page only renders the prompt composer, so it drops TerminalProvider.
// FileProvider and CommentsProvider stay because PromptInput uses file search and comment context.
function DraftProviders(props: ParentProps) {
return (
<FileProvider>
<PromptProvider>
<CommentsProvider>{props.children}</CommentsProvider>
</PromptProvider>
</FileProvider>
)
}
export function AppBaseProviders(
props: ParentProps<{
locale?: Locale
onNativeTranslations?: Parameters<typeof LanguageProvider>[0]["onNativeTranslations"]
onThemeApplied?: () => void
}>,
) {
return (
@ -191,13 +146,14 @@ export function AppBaseProviders(
<ThemeProvider
onThemeApplied={(_, mode, scheme) => {
void window.api?.setTitlebar?.({ mode, scheme })
props.onThemeApplied?.()
}}
>
<LanguageProvider locale={props.locale} onNativeTranslations={props.onNativeTranslations}>
<UiI18nBridge>
<ErrorBoundary
fallback={(error) => {
Sentry.captureException(error)
void import("@sentry/solid").then(({ captureException }) => captureException(error))
return <ErrorPage error={error} />
}}
>

View file

@ -241,7 +241,11 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
sessionId: activeSession.id,
}
const model = tabs.stateValue<PromptSession>(sessionTab, "prompt")?.model.current()
tabs.newDraft({ server: sessionTab.server, directory: activeSession.location.directory }, "", model)
void tabs.newDraft(
{ server: sessionTab.server, directory: activeSession.location.directory },
"",
model,
)
return
}
case "draft": {
@ -249,7 +253,7 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
if (activeTab?.type !== "draft") return
const model = tabs.stateValue<PromptSession>(activeTab, "prompt")?.model.current()
tabs.newDraft({ server: activeTab.server, directory: activeTab.directory }, "", model)
void tabs.newDraft({ server: activeTab.server, directory: activeTab.directory }, "", model)
return
}
case "home": {
@ -263,7 +267,7 @@ export function Titlebar(props: { update?: TitlebarUpdate; debugTools?: { visibl
projects?.list().find((item) => item.worktree === projects.last()) ??
projects?.list()[0]
if (conn && project) {
tabs.newDraft({ server: ServerConnection.key(conn), directory: project.worktree }, "")
void tabs.newDraft({ server: ServerConnection.key(conn), directory: project.worktree }, "")
return
}
}
@ -467,11 +471,14 @@ function ChannelIndicator(props: { debugTools?: { visible: boolean; toggle: () =
)
}
const label = channel && ["local", "beta", "dev"].includes(channel) ? channel.toUpperCase() : undefined
return (
<Show when={["local", "beta", "dev"].includes(channel)}>
<div class="bg-icon-interactive-base text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono">
{channel.toUpperCase()}
</div>
<Show when={label}>
{(value) => (
<div class="bg-icon-interactive-base text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono">
{value()}
</div>
)}
</Show>
)
}

View file

@ -0,0 +1,9 @@
export { AppBaseProviders, AppInterface, preloadRoute } from "./app"
export { ACCEPTED_FILE_EXTENSIONS } from "./constants/file-picker"
export { useCommand } from "./context/command"
export { loadLocaleDict, normalizeLocale, type Locale, useLanguage } from "./context/language"
export { type Platform, PlatformProvider } from "./context/platform"
export { ServerConnection, useServers } from "./context/servers"
export { useTabs } from "./context/tabs"
export { createDraftStore } from "./utils/draft-store"
export { useWslServers } from "./wsl/context"

View file

@ -1,4 +1,4 @@
export { AppBaseProviders, AppInterface } from "./app"
export { AppBaseProviders, AppInterface, preloadRoute } from "./app"
export { useLayout } from "./context/layout"
export { useServerSDK } from "./context/server-sdk"
export { useServers as useServers } from "./context/servers"

View file

@ -0,0 +1,64 @@
import { Navigate, useSearchParams } from "@solidjs/router"
import { createMemo, Show, type ParentProps } from "solid-js"
import { CommentsProvider } from "@/context/comments"
import { FileProvider } from "@/context/file"
import { useGlobal } from "@/context/global"
import { LocationProvider } from "@/context/location"
import { ModelsProvider } from "@/context/models"
import { PromptProvider } from "@/context/prompt"
import { ServerProvider } from "@/context/server"
import { ServerConnection } from "@/context/servers"
import { useTabs, type DraftTab } from "@/context/tabs"
import { SessionUIProvider } from "@/pages/directory-layout"
import NewSession from "@/pages/new-session"
export function DraftRoute() {
const [search] = useSearchParams<{ draftId?: string }>()
const tabs = useTabs()
return (
<Show
when={tabs.store.find((tab): tab is DraftTab => tab.type === "draft" && tab.draftID === search.draftId)}
keyed
fallback={tabs.ready() && <Navigate href="/" />}
>
{(draft) => <ResolvedDraftRoute draft={draft} />}
</Show>
)
}
function ResolvedDraftRoute(props: { draft: DraftTab }) {
const global = useGlobal()
const conn = createMemo(() => global.servers.list().find((item) => ServerConnection.key(item) === props.draft.server))
return (
<Show when={`${props.draft.server}\0${props.draft.directory}`} keyed>
<Show when={conn()} keyed>
{(conn) => (
<ServerProvider conn={conn}>
<ModelsProvider directory={props.draft.directory}>
<LocationProvider directory={props.draft.directory}>
<SessionUIProvider directory={props.draft.directory} server={props.draft.server}>
<DraftProviders>
<NewSession draftId={props.draft.draftID} />
</DraftProviders>
</SessionUIProvider>
</LocationProvider>
</ModelsProvider>
</ServerProvider>
)}
</Show>
</Show>
)
}
// The draft page only renders the prompt composer, so it drops TerminalProvider.
// FileProvider and CommentsProvider stay because PromptInput uses file search and comment context.
function DraftProviders(props: ParentProps) {
return (
<FileProvider>
<PromptProvider>
<CommentsProvider>{props.children}</CommentsProvider>
</PromptProvider>
</FileProvider>
)
}

View file

@ -1,10 +1,8 @@
import { useDirectoryPicker } from "@/components/directory-picker"
import { useServerActionsController } from "@/components/server/server-management-controller"
import { useSettingsCommand } from "@/components/settings-dialog"
import { DialogServerV2 } from "@/components/settings-v2/dialog-server-v2"
import { type LocalProject } from "@/context/layout"
import { useLanguage } from "@/context/language"
import { useNotification } from "@/context/notification"
import { usePlatform } from "@/context/platform"
import { ServerConnection } from "@/context/servers"
import { closeHomeProject, errorMessage, homeProjectDirectories } from "@/pages/layout/helpers"
@ -63,7 +61,11 @@ export function createHomeProjectsController(home: HomeController) {
serverManagement.defaults.set(conn ? ServerConnection.key(conn) : null),
canRemove: (conn: ServerConnection.Any) => serverManagement.connection.canRemove(ServerConnection.key(conn)),
remove: (conn: ServerConnection.Any) => serverManagement.connection.remove(ServerConnection.key(conn)),
edit: (conn: ServerConnection.Http) => dialog.show(() => <DialogServerV2 mode="edit" server={conn} />),
edit: (conn: ServerConnection.Http) => {
void import("@/components/settings-v2/dialog-server-v2").then(({ DialogServerV2 }) => {
void dialog.show(() => <DialogServerV2 mode="edit" server={conn} />)
})
},
focus: home.selection.focusServer,
},
project: {

View file

@ -1,13 +1,14 @@
import { Show, Suspense, type ParentProps } from "solid-js"
import { lazy, Show, Suspense, type ParentProps } from "solid-js"
import { createStore } from "solid-js/store"
import { DebugBar } from "@/components/debug-bar"
import { Titlebar, type TitlebarUpdate } from "@/components/titlebar"
import { usePlatform } from "@/context/platform"
import { ToastRegion } from "@/utils/toast"
const DebugBar = lazy(() => import("@/components/debug-bar").then((module) => ({ default: module.DebugBar })))
export default function Layout(props: ParentProps) {
const platform = usePlatform()
const [state, setState] = createStore({ debugTools: true })
const [state, setState] = createStore({ debugTools: false })
const update: TitlebarUpdate = {
get version() {
@ -41,7 +42,9 @@ export default function Layout(props: ParentProps) {
<Suspense>{props.children}</Suspense>
</main>
<Show when={import.meta.env.DEV && state.debugTools}>
<DebugBar inline />
<Suspense>
<DebugBar inline />
</Suspense>
</Show>
<ToastRegion />
</div>

View file

@ -3,22 +3,56 @@ import { beforeEach, describe, expect, test } from "bun:test"
const src = await Bun.file(new URL("../public/oc-theme-preload.js", import.meta.url)).text()
const run = () => Function(src)()
const setSystemDark = (matches: boolean) =>
Object.defineProperty(window, "matchMedia", {
value: () => ({ matches }) as MediaQueryList,
configurable: true,
})
beforeEach(() => {
document.head.innerHTML = ""
document.documentElement.removeAttribute("data-theme")
document.documentElement.removeAttribute("data-color-scheme")
document.documentElement.style.removeProperty("background-color")
localStorage.clear()
Object.defineProperty(window, "matchMedia", {
value: () =>
({
matches: false,
}) as MediaQueryList,
configurable: true,
})
setSystemDark(false)
})
describe("theme preload", () => {
test("uses default theme and system light mode when settings are absent", () => {
run()
expect(document.documentElement.dataset.theme).toBe("oc-2")
expect(document.documentElement.dataset.colorScheme).toBe("light")
expect(document.documentElement.style.backgroundColor).toBe("#fafafa")
})
test("restores explicit dark mode on a light system", () => {
localStorage.setItem("opencode-color-scheme", "dark")
run()
expect(document.documentElement.dataset.colorScheme).toBe("dark")
expect(document.documentElement.style.backgroundColor).toBe("#080808")
})
test("restores explicit light mode on a dark system", () => {
setSystemDark(true)
localStorage.setItem("opencode-color-scheme", "light")
run()
expect(document.documentElement.dataset.colorScheme).toBe("light")
expect(document.documentElement.style.backgroundColor).toBe("#fafafa")
})
test("resolves persisted system mode before paint", () => {
setSystemDark(true)
localStorage.setItem("opencode-color-scheme", "system")
run()
expect(document.documentElement.dataset.colorScheme).toBe("dark")
expect(document.documentElement.style.backgroundColor).toBe("#080808")
})
test("keeps cached css for non-default themes", () => {
localStorage.setItem("opencode-theme-id", "nightowl")
localStorage.setItem("opencode-theme-css-light", "--background-base:#fff;")
@ -28,4 +62,15 @@ describe("theme preload", () => {
expect(document.documentElement.dataset.theme).toBe("nightowl")
expect(document.getElementById("oc-theme-preload")?.textContent).toContain("--background-base:#fff;")
})
test("restores the cached variant for a persisted custom dark theme", () => {
localStorage.setItem("opencode-theme-id", "nightowl")
localStorage.setItem("opencode-color-scheme", "dark")
localStorage.setItem("opencode-theme-css-dark", "--background-base:#010203;")
run()
expect(document.documentElement.dataset.theme).toBe("nightowl")
expect(document.documentElement.dataset.colorScheme).toBe("dark")
expect(document.getElementById("oc-theme-preload")?.textContent).toContain("--background-base:#010203;")
})
})

View file

@ -1,6 +1,6 @@
import { sentryVitePlugin } from "@sentry/vite-plugin"
import { defineConfig } from "vite"
import desktopPlugin from "./vite"
import desktopPlugin from "./vite.js"
const sentry =
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT

View file

@ -4,6 +4,18 @@ import tailwindcss from "@tailwindcss/vite"
import { fileURLToPath } from "url"
const theme = fileURLToPath(new URL("./public/oc-theme-preload.js", import.meta.url))
const themeScript = readFileSync(theme, "utf8")
const tailwind = tailwindcss()
const tailwindGenerate = tailwind.find((plugin) => plugin.name === "@tailwindcss/vite:generate:serve")
const tailwindHotUpdate = tailwindGenerate?.hotUpdate
// Tailwind 4.3.3 expects a server that Vite's bundled dev hook does not provide.
if (tailwindGenerate && typeof tailwindHotUpdate === "function") {
tailwindGenerate.hotUpdate = function (context) {
if (!context.server) return
return tailwindHotUpdate.call(this, context)
}
}
const channel = (() => {
const raw = process.env.OPENCODE_CHANNEL
@ -40,13 +52,18 @@ export default [
},
{
name: "opencode-desktop:theme-preload",
transformIndexHtml(html) {
return html.replace(
'<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>',
`<script id="oc-theme-preload-script">${readFileSync(theme, "utf8")}</script>`,
)
transformIndexHtml: {
order: "pre",
handler: inlineThemePreload,
},
},
tailwindcss(),
...tailwind,
solidPlugin(),
]
export function inlineThemePreload(html) {
return html.replace(
/<script id="oc-theme-preload-script" src="(?:\.\/|\/)oc-theme-preload\.js"><\/script>/,
`<script id="oc-theme-preload-script">${themeScript}</script>`,
)
}

View file

@ -12,8 +12,8 @@ export type EnsureTiming = {
const timings = new WeakMap<object, EnsureTiming>()
export const defaultEnsureTiming: EnsureTiming = {
pollInterval: 1_000,
attempts: 120,
pollInterval: 100,
attempts: 1_200,
requestTimeout: 2_000,
spawnDelay: 5_000,
maxSpawnDelay: 30_000,

View file

@ -2,6 +2,7 @@ import { withEnsureTiming } from "../../src/service-timing"
const timing = {
pollInterval: 20,
attempts: 120,
requestTimeout: 100,
spawnDelay: 200,
maxSpawnDelay: 1_200,

View file

@ -1,6 +1,4 @@
import { sentryVitePlugin } from "@sentry/vite-plugin"
import { defineConfig } from "electron-vite"
import appPlugin from "@opencode-ai/app/vite"
const channel = (() => {
const raw = process.env.OPENCODE_CHANNEL
@ -11,9 +9,10 @@ const channel = (() => {
const nodePtyPkg = `@lydell/node-pty-${process.platform}-${process.arch}`
const appPlugin = (await import("@opencode-ai/app/vite")).default
const sentry =
process.env.SENTRY_AUTH_TOKEN && process.env.SENTRY_ORG && process.env.SENTRY_PROJECT
? sentryVitePlugin({
? (await import("@sentry/vite-plugin")).sentryVitePlugin({
authToken: process.env.SENTRY_AUTH_TOKEN,
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
@ -34,11 +33,12 @@ export default defineConfig({
"import.meta.env.OPENCODE_CHANNEL": JSON.stringify(channel),
},
build: {
rollupOptions: {
rolldownOptions: {
input: { index: "src/main/index.ts" },
// Keep this identical to electron-vite's Node 20.11+ shim. Its regex insertion can
// corrupt bundled TypeScript, while a Rollup banner places the shim safely.
// corrupt bundled TypeScript, while an output banner places the shim safely.
output: {
format: "es",
banner: `
// -- CommonJS Shims --
import __cjs_mod__ from 'node:module';
@ -56,13 +56,14 @@ const require = __cjs_mod__.createRequire(import.meta.url);
enforce: "pre",
resolveId(s) {
if (s === "@lydell/node-pty") return nodePtyPkg
return undefined
},
},
],
},
preload: {
build: {
rollupOptions: {
rolldownOptions: {
input: { index: "src/preload/index.ts" },
output: {
format: "cjs",
@ -72,6 +73,9 @@ const require = __cjs_mod__.createRequire(import.meta.url);
},
},
renderer: {
experimental: {
bundledDev: true,
},
define: {
"import.meta.env.OPENCODE_VERSION": JSON.stringify(process.env.OPENCODE_VERSION),
"import.meta.env.VITE_OPENCODE_CHANNEL": JSON.stringify(channel),
@ -81,7 +85,7 @@ const require = __cjs_mod__.createRequire(import.meta.url);
root: "src/renderer",
build: {
sourcemap: true,
rollupOptions: {
rolldownOptions: {
input: {
main: "src/renderer/index.html",
},

View file

@ -40,7 +40,6 @@
"@opencode-ai/ui": "workspace:*",
"@sentry/solid": "catalog:",
"@sentry/vite-plugin": "catalog:",
"@solid-primitives/i18n": "2.2.1",
"@solid-primitives/storage": "catalog:",
"@solidjs/meta": "catalog:",
"@solidjs/router": "0.15.4",
@ -50,11 +49,11 @@
"@valibot/to-json-schema": "1.6.0",
"electron": "42.3.3",
"electron-builder": "26.15.2",
"electron-vite": "^5",
"electron-vite": "6.0.0-beta.1",
"solid-js": "catalog:",
"sury": "11.0.0-alpha.4",
"typescript": "~5.6.2",
"vite": "catalog:",
"vite": "8.2.1",
"zod-openapi": "5.4.6"
},
"optionalDependencies": {

View file

@ -11,14 +11,17 @@ async function main() {
process.env.OPENCODE_DISABLE_CHANNEL_DB = "0"
const options = selectOptions()
if (options.server.type === "build") process.env.OPENCODE_DESKTOP_SERVER_CHANNEL = "local"
process.env.OPENCODE_DESKTOP_ISOLATED_SERVER = "1"
await prepareDesktop()
await prepareServer(options.server)
await startDesktop(options.electron)
}
async function prepareDesktop() {
await $`bun run install-electron`
await $`bun ./scripts/copy-icons.ts ${process.env.OPENCODE_CHANNEL ?? "dev"}`
await Promise.all([
$`bun run install-electron`,
$`bun ./scripts/copy-icons.ts ${process.env.OPENCODE_CHANNEL ?? "dev"}`,
])
}
function selectOptions(): DevOptions {
@ -46,7 +49,6 @@ async function prepareServer(source: ServerSource) {
}
async function startDesktop(args: string[]) {
process.env.OPENCODE_DESKTOP_ISOLATED_SERVER = "1"
await $`electron-vite dev ${args}`
}

View file

@ -2,7 +2,12 @@ import { app } from "electron"
import { Deferred, Effect, Fiber } from "effect"
import type { ServerReadyData } from "../shared/ipc-contract"
import { checkAppExists, resolveAppPath } from "./files/apps"
import { registerIpcHandlers, registerUpdaterIpcHandlers, registerWslIpcHandlers } from "./ipc"
import {
registerIpcHandlers,
registerUpdaterIpcHandlers,
registerWslInitialization,
registerWslIpcHandlers,
} from "./ipc"
import {
acquireApplicationLock,
configureApplication,
@ -26,13 +31,17 @@ const main = Effect.gen(function* () {
const logger = configureApplication()
if (!acquireApplicationLock()) return
preferApplicationEnvironment(logger)
loadProxyEnvironment(logger)
const lifecycle = createApplicationLifecycle(logger)
const serverReady = Deferred.makeUnsafe<ServerReadyData, unknown>()
const wslReady = Promise.withResolvers<void>()
logger.log("starting v2 background service")
const backgroundTask = yield* Effect.promise(() => startBackgroundCli(logger)).pipe(Effect.forkChild)
yield* Effect.promise(() => app.whenReady())
yield* prepareDesktop(logger)
const updater = setupAutoUpdater(lifecycle.prepareToRestart)
const updater = yield* Effect.promise(() => setupAutoUpdater(lifecycle.prepareToRestart))
const menu = {
trigger: (id: string) => {
const win = getLastFocusedWindow()
@ -68,27 +77,35 @@ const main = Effect.gen(function* () {
},
})
registerUpdaterIpcHandlers(createUpdaterIpc(updater))
registerWslInitialization(wslReady.promise)
startAutoUpdater(updater)
yield* Effect.promise(() => startNetworkLogging())
const loadingTask = yield* Effect.gen(function* () {
loadProxyEnvironment(logger)
logger.log("starting v2 background service")
const background = yield* Effect.promise(() => startBackgroundCli(logger))
const wsl = yield* Effect.promise(() => startWsl(background, logger))
registerWslIpcHandlers(wsl.ipc)
wsl.start()
lifecycle.setWslShutdown(wsl.stop)
const background = yield* Fiber.join(backgroundTask)
yield* Deferred.succeed(serverReady, {
url: background.url,
username: background.username,
password: background.password,
})
logger.log("loading task finished")
void startWsl(background, logger).then(
(wsl) => {
registerWslIpcHandlers(wsl.ipc)
lifecycle.setWslShutdown(wsl.stop)
wsl.start()
wslReady.resolve()
},
(error) => {
logger.error("failed to start WSL manager", { error })
wslReady.reject(error)
},
)
}).pipe(forwardInitializationFailure(serverReady), Effect.forkChild)
yield* Fiber.await(loadingTask)
if (lifecycle.restoreWindows().length) createMenu(menu)
yield* Fiber.await(loadingTask)
})
Effect.runFork(main)

View file

@ -15,7 +15,14 @@ import { createFileCapabilities, openExternalURL, openLocalFileURL } from "./fil
import { setForceFocus } from "./native/debug"
import { runDesktopMenuAction } from "./native/menu-actions"
import { createDesktopStorage } from "./storage"
import { getPinchZoomEnabled, getWindowID, setPinchZoomEnabled, setTitlebar, updateTitlebar } from "./windows"
import {
getPinchZoomEnabled,
getWindowID,
setPinchZoomEnabled,
setTitlebar,
setWindowThemeReady,
updateTitlebar,
} from "./windows"
import type { UpdaterIpc } from "./updater"
import type { WslIpc } from "./wsl/ipc"
@ -112,6 +119,12 @@ export function registerIpcHandlers(deps: Deps) {
return id
})
handle(Ipc.window.themeReady, (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
if (!win) throw new Error("Window not found")
setWindowThemeReady(win)
})
handle(Ipc.window.getFocused, (event) => {
const win = BrowserWindow.fromWebContents(event.sender)
return win?.isFocused() ?? false
@ -167,6 +180,10 @@ export function registerUpdaterIpcHandlers(updater: UpdaterIpc) {
handle(Ipc.updater.install, () => updater.install())
}
export function registerWslInitialization(ready: Promise<void>) {
handle(Ipc.wsl.awaitInitialization, () => ready)
}
export function registerWslIpcHandlers(wsl: WslIpc) {
handle(Ipc.wsl.subscribe, (event) => wsl.subscribe(event.sender))
handle(Ipc.wsl.unsubscribe, (event) => wsl.unsubscribe(event.sender.id))

View file

@ -35,11 +35,11 @@ export function configureApplication() {
process.env.OPENCODE_DISABLE_EMBEDDED_WEB_UI = "true"
const appID = app.isPackaged ? appIDs[CHANNEL] : "ai.opencode.desktop.dev"
const onboardingRoot = createOnboardingTestRoot()
const testRoot = createTestRoot()
app.setName(app.isPackaged ? appNames[CHANNEL] : "OpenCode Dev")
app.setAppUserModelId(appID)
app.setPath("userData", onboardingRoot ? join(onboardingRoot, "desktop") : join(app.getPath("appData"), appID))
if (onboardingRoot) app.setPath("sessionData", join(onboardingRoot, "session"))
app.setPath("userData", testRoot ? join(testRoot, "desktop") : join(app.getPath("appData"), appID))
if (testRoot) app.setPath("sessionData", join(testRoot, "session"))
initializeFirstLaunchOnboarding(app.getPath("userData"))
const logger = initLogging()
@ -48,14 +48,15 @@ export function configureApplication() {
logger.log("app starting", {
version: VERSION,
packaged: app.isPackaged,
onboardingTest: Boolean(onboardingRoot),
onboardingTest: testOnboarding,
})
loadProxyEnvironment(logger)
app.commandLine.appendSwitch("proxy-bypass-list", "<-loopback>")
const features = app.commandLine.getSwitchValue("enable-features")
app.commandLine.appendSwitch("enable-features", features ? `${jsCallStackFeature},${features}` : jsCallStackFeature)
if (!app.isPackaged) app.commandLine.appendSwitch("remote-debugging-port", "9222")
if (!app.isPackaged)
app.commandLine.appendSwitch("remote-debugging-port", process.env.OPENCODE_DESKTOP_REMOTE_DEBUGGING_PORT ?? "9222")
return logger
}
@ -88,7 +89,8 @@ export function prepareDesktop(logger: DesktopLogger) {
),
Effect.catch((error) => Effect.sync(() => logger.warn("failed to clean scoped store files", error))),
)
app.setAsDefaultProtocolClient("opencode")
if (app.isPackaged || process.env.OPENCODE_DESKTOP_DISABLE_PROTOCOL_REGISTRATION !== "1")
app.setAsDefaultProtocolClient("opencode")
registerRendererProtocol()
setDockIcon()
})
@ -105,14 +107,18 @@ export function loadProxyEnvironment(logger: DesktopLogger) {
}
}
function createOnboardingTestRoot() {
if (!testOnboarding) return undefined
const root = join(tmpdir(), `opencode-onboarding-${randomUUID()}`)
rmSync(root, { recursive: true, force: true })
function createTestRoot() {
const root = testOnboarding
? join(tmpdir(), `opencode-onboarding-${randomUUID()}`)
: app.isPackaged
? undefined
: process.env.OPENCODE_DESKTOP_TEST_ROOT
if (!root) return undefined
if (testOnboarding) rmSync(root, { recursive: true, force: true })
;["data", "config", "cache", "state", "desktop", "session"].forEach((dir) =>
mkdirSync(join(root, dir), { recursive: true }),
)
process.env.OPENCODE_DB = ":memory:"
if (testOnboarding) process.env.OPENCODE_DB = ":memory:"
process.env.XDG_DATA_HOME = join(root, "data")
process.env.XDG_CONFIG_HOME = join(root, "config")
process.env.XDG_CACHE_HOME = join(root, "cache")

View file

@ -2,7 +2,6 @@ import { MainLogger } from "electron-log"
import log from "electron-log/main.js"
import { app, crashReporter, netLog, shell } from "electron"
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from "node:fs"
import { ZipWriter, BlobWriter, BlobReader } from "@zip.js/zip.js"
import { dirname, join } from "node:path"
import { homedir } from "node:os"
import { VERSION } from "../constants"
@ -185,6 +184,7 @@ function collect(dir: string, prefix: string): Entry[] {
}
async function writeZip(output: string, entries: Entry[]) {
const { BlobReader, BlobWriter, ZipWriter } = await import("@zip.js/zip.js")
const writer = new ZipWriter(new BlobWriter("application/zip"))
for (const entry of entries) {
const data = entry.data ?? readFileSync(entry.path!)

View file

@ -3,4 +3,5 @@ export const DEFAULT_SERVER_URL_KEY = "defaultServerUrl"
export const FIRST_LAUNCH_ONBOARDING_COMPLETE_KEY = "firstLaunchOnboardingComplete"
export const WSL_SERVERS_KEY = "wslServers"
export const PINCH_ZOOM_ENABLED_KEY = "pinchZoomEnabled"
export const BACKGROUND_COLOR_KEY = "backgroundColor"
export const WINDOW_IDS_KEY = "windowIds"

View file

@ -6,21 +6,22 @@ import { getLogger } from "../native/logging"
import { nativeT } from "../native/translations"
import { getStore } from "../storage/store"
import { createUpdaterController, type UpdaterController, type UpdaterReadyRecord } from "./controller"
import { createUpdaterPlatform } from "./platform"
const key = "ready"
export function setupAutoUpdater(prepareToRestart: () => Promise<void>) {
export async function setupAutoUpdater(prepareToRestart: () => Promise<void>) {
const logger = getLogger()
const store = getStore("opencode.updater")
const platform = UPDATER_ENABLED ? (await import("./platform")).createUpdaterPlatform(logger) : undefined
return createUpdaterController({
currentVersion: app.getVersion(),
platform: UPDATER_ENABLED ? createUpdaterPlatform(logger) : undefined,
platform,
lifecycle: { prepareToRestart },
persistence: {
get() {
const value = store.get(key)
if (!value || typeof value !== "object" || !("version" in value) || typeof value.version !== "string") return
if (!value || typeof value !== "object" || !("version" in value) || typeof value.version !== "string")
return undefined
return { version: value.version } satisfies UpdaterReadyRecord
},
set: (value) => store.set(key, value),

View file

@ -5,7 +5,7 @@ import { app, BrowserWindow, nativeImage, nativeTheme } from "electron"
import { join } from "node:path"
import { Ipc, sendIpcEvent, type TitlebarTheme } from "../../shared/ipc-contract"
import { developmentResourcesRoot, preloadPath } from "../paths"
import { PINCH_ZOOM_ENABLED_KEY } from "../storage/keys"
import { BACKGROUND_COLOR_KEY, PINCH_ZOOM_ENABLED_KEY } from "../storage/keys"
import { getStore } from "../storage/store"
const oc2Theme = oc2ThemeJson as DesktopTheme
@ -22,10 +22,12 @@ let backgroundColor: string | undefined
export function windowAppearance() {
const mode = tone()
const storedBackground = getStore().get(BACKGROUND_COLOR_KEY)
return {
title: "OpenCode",
icon: iconPath(),
backgroundColor: backgroundColor ?? oc2Background[mode],
backgroundColor:
backgroundColor ?? (typeof storedBackground === "string" ? storedBackground : undefined) ?? oc2Background[mode],
...(process.platform === "darwin"
? {
titleBarStyle: "hidden" as const,
@ -56,6 +58,7 @@ export function setDockIcon() {
export function setBackgroundColor(color: string) {
backgroundColor = color
getStore().set(BACKGROUND_COLOR_KEY, color)
BrowserWindow.getAllWindows().forEach((win) => {
win.setBackgroundColor(color)
if (process.platform === "darwin") win.invalidateShadow()
@ -63,7 +66,8 @@ export function setBackgroundColor(color: string) {
}
export function getBackgroundColor() {
return backgroundColor
const stored = getStore().get(BACKGROUND_COLOR_KEY)
return backgroundColor ?? (typeof stored === "string" ? stored : undefined)
}
export function setTitlebar(win: BrowserWindow, theme: Partial<TitlebarTheme> = {}) {

View file

@ -3,6 +3,7 @@ import { randomUUID } from "node:crypto"
import { rmSync } from "node:fs"
import { join } from "node:path"
import { app, BrowserWindow } from "electron"
import { writeLog } from "../native/logging"
import { removeStoreFile, getStore } from "../storage/store"
import { WINDOW_IDS_KEY } from "../storage/keys"
import {
@ -23,6 +24,7 @@ import { wireWindowRecovery } from "./recovery"
import { allowRendererPermissions, wireNavigationPolicy, wireRendererHeaders } from "./security"
const windowIDs = new WeakMap<BrowserWindow, string>()
const themeReady = new WeakMap<BrowserWindow, () => void>()
const registry = createWindowRegistry<BrowserWindow>({
read: () => getStore().get(WINDOW_IDS_KEY),
write: (ids) => getStore().set(WINDOW_IDS_KEY, ids),
@ -68,6 +70,10 @@ export function getLastFocusedWindow() {
return win
}
export function setWindowThemeReady(win: BrowserWindow) {
themeReady.get(win)?.()
}
export function restoreMainWindows() {
const ids = registry.persisted()
return (ids.length ? ids : [randomUUID()]).map((id) => createMainWindow(id))
@ -92,16 +98,28 @@ export function createMainWindow(id: string = randomUUID()) {
state.manage(win)
registerWindow(win, id)
wireFullscreen(win)
loadWindow(win, "index.html")
wireZoom(win)
let contentReady = false
let appliedTheme = false
let revealed = false
const reveal = () => {
if (revealed || win.isDestroyed()) return
if (!contentReady || !appliedTheme || revealed || win.isDestroyed()) return
revealed = true
win.show()
writeLog("window", "main window visible", { window: id })
}
win.once("ready-to-show", reveal)
if (process.platform === "linux") win.webContents.once("did-finish-load", reveal)
const ready = () => {
contentReady = true
reveal()
}
themeReady.set(win, () => {
appliedTheme = true
reveal()
})
win.once("ready-to-show", ready)
if (process.platform === "linux") win.webContents.once("did-finish-load", ready)
win.once("closed", () => themeReady.delete(win))
loadWindow(win, "index.html")
return win
}

View file

@ -34,29 +34,36 @@ const updaterHandler = (state: UpdaterState) => {
updaterState = state
updaterCallbacks.forEach((callback) => callback(state))
}
type WslInvoke = Exclude<
(typeof Ipc.wsl)[keyof typeof Ipc.wsl],
typeof Ipc.wsl.awaitInitialization | typeof Ipc.wsl.event
>
function invokeWsl<Channel extends WslInvoke>(channel: Channel, ...args: IpcInvokeArgs<Channel>) {
return invoke(Ipc.wsl.awaitInitialization).then(() => invoke(channel, ...args))
}
const api: ElectronAPI = {
awaitInitialization: () => invoke(Ipc.app.awaitInitialization),
wslServers: {
getState: () => invoke(Ipc.wsl.getState),
getState: () => invokeWsl(Ipc.wsl.getState),
subscribe: (cb) => {
const dispose = listen(Ipc.wsl.event, cb)
void invoke(Ipc.wsl.subscribe)
const subscribed = invokeWsl(Ipc.wsl.subscribe)
return () => {
dispose()
void invoke(Ipc.wsl.unsubscribe)
void subscribed.then(() => invokeWsl(Ipc.wsl.unsubscribe))
}
},
probeRuntime: () => invoke(Ipc.wsl.probeRuntime),
refreshDistros: () => invoke(Ipc.wsl.refreshDistros),
installWsl: () => invoke(Ipc.wsl.installWsl),
installDistro: (name) => invoke(Ipc.wsl.installDistro, name),
probeAddable: (distros) => invoke(Ipc.wsl.probeAddable, distros),
installOpencode: (name) => invoke(Ipc.wsl.installOpencode, name),
openTerminal: (name) => invoke(Ipc.wsl.openTerminal, name),
addServer: (distro) => invoke(Ipc.wsl.addServer, distro),
removeServer: (id) => invoke(Ipc.wsl.removeServer, id),
startServer: (id) => invoke(Ipc.wsl.startServer, id),
probeRuntime: () => invokeWsl(Ipc.wsl.probeRuntime),
refreshDistros: () => invokeWsl(Ipc.wsl.refreshDistros),
installWsl: () => invokeWsl(Ipc.wsl.installWsl),
installDistro: (name) => invokeWsl(Ipc.wsl.installDistro, name),
probeAddable: (distros) => invokeWsl(Ipc.wsl.probeAddable, distros),
installOpencode: (name) => invokeWsl(Ipc.wsl.installOpencode, name),
openTerminal: (name) => invokeWsl(Ipc.wsl.openTerminal, name),
addServer: (distro) => invokeWsl(Ipc.wsl.addServer, distro),
removeServer: (id) => invokeWsl(Ipc.wsl.removeServer, id),
startServer: (id) => invokeWsl(Ipc.wsl.startServer, id),
},
updater: {
subscribe: async (cb) => {
@ -100,6 +107,7 @@ const api: ElectronAPI = {
draftBlobGet: (id) => invoke(Ipc.drafts.getBlob, id),
getWindowID: () => invoke(Ipc.window.getId),
themeReady: () => invoke(Ipc.window.themeReady),
onMenuCommand: (cb) => listen(Ipc.menu.command, cb),
onDeepLink: (cb) => listen(Ipc.app.deepLink, cb),

View file

@ -38,6 +38,7 @@ export type ElectronAPI = {
draftBlobGet: IpcInvokeMethod<typeof Ipc.drafts.getBlob>
getWindowID: IpcInvokeMethod<typeof Ipc.window.getId>
themeReady: IpcInvokeMethod<typeof Ipc.window.themeReady>
onMenuCommand: IpcEventSubscription<typeof Ipc.menu.command>
onDeepLink: IpcEventSubscription<typeof Ipc.app.deepLink>

View file

@ -5,6 +5,7 @@ import {
AppBaseProviders,
AppInterface,
PlatformProvider,
preloadRoute,
ServerConnection,
useCommand,
useLanguage,
@ -13,9 +14,8 @@ import {
} from "@opencode-ai/app"
import { useTheme } from "@opencode-ai/ui/theme/context"
import type { BaseRouterProps } from "@solidjs/router"
import { createEffect, createMemo, createResource, Show } from "solid-js"
import { createEffect, createMemo, createResource, lazy, Show, Suspense } from "solid-js"
import type { ElectronAPI } from "../preload/types"
import { MigrationStatus } from "./migration-status"
import { DesktopFirstLaunchOnboarding } from "./onboarding"
import { createDesktopPlatform, type DesktopWindowState } from "./platform"
import { bindDesktopMenu } from "./platform/menu"
@ -26,6 +26,8 @@ import { getLastActiveUrl } from "./window/route-storage"
import { DesktopMemoryRouter } from "./window/router"
import { availableStartupServer, readyWslConnections } from "./wsl/connections"
const MigrationStatus = lazy(() => import("./migration-status").then((module) => ({ default: module.MigrationStatus })))
export function DesktopApp(props: { api: ElectronAPI; updater: UpdaterPlatform; version: string }) {
const [windowState] = createResource(() => props.api.getWindowID().then((id) => ({ id, version: props.version })))
return (
@ -37,9 +39,11 @@ export function DesktopApp(props: { api: ElectronAPI; updater: UpdaterPlatform;
function DesktopWindow(props: { api: ElectronAPI; updater: UpdaterPlatform; windowState: DesktopWindowState }) {
const platform = createDesktopPlatform(props.api, props.windowState, props.updater)
const initialUrl = getLastActiveUrl(props.windowState.id)
const [sidecar] = createResource(() => props.api.awaitInitialization())
const [defaultServer] = createResource(() => platform.getDefaultServer?.())
const [locale] = createResource(() => preloadStoredLocale(platform))
const [route] = createResource(() => preloadRoute(initialUrl))
const router = (routerProps: BaseRouterProps) => (
<DesktopMemoryRouter {...routerProps} windowID={props.windowState.id} />
)
@ -47,9 +51,7 @@ function DesktopWindow(props: { api: ElectronAPI; updater: UpdaterPlatform; wind
function ReadyApp() {
const wslServers = useWslServers()
const language = useLanguage()
const ready = createMemo(
() => !defaultServer.loading && !sidecar.loading && !locale.loading && !wslServers.isLoading,
)
const ready = createMemo(() => !defaultServer.loading && !sidecar.loading && !locale.loading && !route.loading)
const servers = createMemo(() => {
const data = initializationData(sidecar)
const list: ServerConnection.Any[] = []
@ -79,13 +81,15 @@ function DesktopWindow(props: { api: ElectronAPI; updater: UpdaterPlatform; wind
<AppInterface defaultServer={key} servers={servers()} router={router}>
<DesktopFirstLaunchOnboarding
api={props.api}
initialUrl={getLastActiveUrl(props.windowState.id)}
initialUrl={initialUrl}
serverKey={key}
/>
<DesktopEffects api={props.api} />
<Show when={initializationData(sidecar)} keyed>
{(server) => <MigrationStatus server={server} />}
</Show>
<Suspense fallback={null}>
<Show when={initializationData(sidecar)} keyed>
{(server) => <MigrationStatus server={server} />}
</Show>
</Suspense>
</AppInterface>
)}
</Show>
@ -98,6 +102,7 @@ function DesktopWindow(props: { api: ElectronAPI; updater: UpdaterPlatform; wind
<AppBaseProviders
locale={locale.latest}
onNativeTranslations={(bundle) => void props.api.setNativeTranslations(bundle).catch(() => undefined)}
onThemeApplied={() => void props.api.themeReady()}
>
<Show when={true}>{(_) => <ReadyApp />}</Show>
</AppBaseProviders>

View file

@ -1,21 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "ዝማኔዎችን ይመልከቱ...",
"desktop.menu.reloadWebview": "ዳግም ጫን Webview",
"desktop.menu.restart": "ዳግም አስጀምር",
"desktop.dialog.chooseFolder": "አቃፊ ምረጥ",
"desktop.dialog.chooseFile": "ፋይል ምረጥ",
"desktop.dialog.saveFile": "ፋይሉን አስቀምጥ",
"desktop.updater.checkFailed.title": "ማዘመን ቼክ አልተሳካም",
"desktop.updater.checkFailed.message": "ዝማኔዎችን ማረጋገጥ አልተሳካም",
"desktop.updater.none.title": "ምንም ማሻሻያ የለም",
"desktop.updater.none.message": "አሁን የቅርብ ጊዜውን የOpenCode ስሪት እየተጠቀሙ ነው",
"desktop.updater.downloadFailed.title": "ዝማኔ አልተሳካም",
"desktop.updater.downloadFailed.message": "ዝማኔን ማውረድ አልተሳካም",
"desktop.updater.downloaded.title": "ዝማኔው ወርዷል",
"desktop.updater.downloaded.prompt": "ስሪት {{version}} ከOpenCode ወርዷል፣ መጫን እና እንደገና ማስጀመር ይፈልጋሉ?",
"desktop.updater.installFailed.title": "ዝማኔ አልተሳካም",
"desktop.updater.installFailed.message": "ዝማኔን መጫን አልተሳካም",
"desktop.error.dev.rootNotFound":
"ሥርወ አካል አልተገኘም። ወደ የእርስዎ index.html ማከልን ረስተዋል? ወይም የመታወቂያ ባህሪው የተሳሳተ ፊደል ተጽፎ ሊሆን ይችላል?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "التحقق من وجود تحديثات...",
"desktop.menu.reloadWebview": "إعادة تحميل عرض الويب",
"desktop.menu.restart": "إعادة تشغيل",
"desktop.dialog.chooseFolder": "اختيار مجلد",
"desktop.dialog.chooseFile": "اختيار ملف",
"desktop.dialog.saveFile": "حفظ ملف",
"desktop.updater.checkFailed.title": "فشل التحقق من التحديثات",
"desktop.updater.checkFailed.message": "فشل التحقق من وجود تحديثات",
"desktop.updater.none.title": "لا توجد تحديثات متاحة",
"desktop.updater.none.message": "أنت تستخدم بالفعل أحدث إصدار من OpenCode",
"desktop.updater.downloadFailed.title": "فشل التحديث",
"desktop.updater.downloadFailed.message": "فشل تنزيل التحديث",
"desktop.updater.downloaded.title": "تم تنزيل التحديث",
"desktop.updater.downloaded.prompt":
"تم تنزيل الإصدار {{version}} من OpenCode. هل ترغب في تثبيته وإعادة تشغيل التطبيق؟",
"desktop.updater.installFailed.title": "فشل التحديث",
"desktop.updater.installFailed.message": "فشل تثبيت التحديث",
"desktop.error.dev.rootNotFound":
"لم يتم العثور على العنصر الجذري. هل نسيت إضافته إلى index.html؟ أو ربما تمت كتابة سمة id بشكل خاطئ؟",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Yeniləmələri yoxla...",
"desktop.menu.reloadWebview": "Webview-u yenidən yüklə",
"desktop.menu.restart": "Yenidən başlat",
"desktop.dialog.chooseFolder": "Qovluq seçin",
"desktop.dialog.chooseFile": "Fayl seçin",
"desktop.dialog.saveFile": "Faylı saxla",
"desktop.updater.checkFailed.title": "Yeniləmə yoxlaması uğursuz oldu",
"desktop.updater.checkFailed.message": "Yeniləmələr yoxlana bilmədi",
"desktop.updater.none.title": "Yeniləmə mövcud deyil",
"desktop.updater.none.message": "Artıq OpenCode-un ən son versiyasından istifadə edirsiniz",
"desktop.updater.downloadFailed.title": "Yeniləmə uğursuz oldu",
"desktop.updater.downloadFailed.message": "Yeniləmə yüklənə bilmədi",
"desktop.updater.downloaded.title": "Yeniləmə yükləndi",
"desktop.updater.downloaded.prompt":
"OpenCode-un {{version}} versiyası yüklənib. Onu quraşdırıb tətbiqi yenidən başlatmaq istəyirsiniz?",
"desktop.updater.installFailed.title": "Yeniləmə uğursuz oldu",
"desktop.updater.installFailed.message": "Yeniləmə quraşdırıla bilmədi",
"desktop.error.dev.rootNotFound":
"Kök element tapılmadı. index.html-ə əlavə etməyi unutmusunuz? Yoxsa id atributu səhv yazılıb?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Проверете за актуализации...",
"desktop.menu.reloadWebview": "Презареди Webview",
"desktop.menu.restart": "Рестартирайте",
"desktop.dialog.chooseFolder": "Изберете папка",
"desktop.dialog.chooseFile": "Изберете файл",
"desktop.dialog.saveFile": "Запазете файла",
"desktop.updater.checkFailed.title": "Проверката на актуализацията е неуспешна",
"desktop.updater.checkFailed.message": "Неуспешна проверка за актуализации",
"desktop.updater.none.title": "Няма налична актуализация",
"desktop.updater.none.message": "Вече използвате най-новата версия на OpenCode",
"desktop.updater.downloadFailed.title": "Неуспешна актуализация",
"desktop.updater.downloadFailed.message": "Неуспешно изтегляне на актуализация",
"desktop.updater.downloaded.title": "Актуализацията е изтеглена",
"desktop.updater.downloaded.prompt":
"Версия {{version}} от OpenCode е изтеглена. Искате ли да я инсталирате и рестартирате?",
"desktop.updater.installFailed.title": "Неуспешна актуализация",
"desktop.updater.installFailed.message": "Неуспешно инсталиране на актуализация",
"desktop.error.dev.rootNotFound":
"Основният елемент не е намерен. Забравихте ли да го добавите към вашия index.html? Или може би атрибутът id е изписан неправилно?",
}

View file

@ -1,22 +0,0 @@
export const dict: Record<string, string> = {
"desktop.menu.checkForUpdates": "আপডেটের জন্য চেক করুন...",
"desktop.menu.reloadWebview": "Webview পুনরায় লোড করুন",
"desktop.menu.restart": "রিস্টার্ট করুন",
"desktop.dialog.chooseFolder": "একটি ফোল্ডার নির্বাচন করুন",
"desktop.dialog.chooseFile": "একটি ফাইল নির্বাচন করুন",
"desktop.dialog.saveFile": "ফাইল সংরক্ষণ করুন",
"desktop.updater.checkFailed.title": "আপডেট চেক ব্যর্থ হয়েছে",
"desktop.updater.checkFailed.message": "আপডেটের জন্য চেক করতে ব্যর্থ",
"desktop.updater.none.title": "কোন আপডেট উপলব্ধ নেই",
"desktop.updater.none.message": "আপনি ইতিমধ্যেই OpenCode এর সর্বশেষ সংস্করণ ব্যবহার করছেন৷",
"desktop.updater.downloadFailed.title": "আপডেট ব্যর্থ হয়েছে৷",
"desktop.updater.downloadFailed.message": "আপডেট ডাউনলোড করতে ব্যর্থ হয়েছে",
"desktop.updater.downloaded.title": "আপডেট ডাউনলোড হয়েছে",
"desktop.updater.downloaded.prompt":
"OpenCode-এর {{version}} সংস্করণ ডাউনলোড করা হয়েছে, আপনি কি এটি ইনস্টল করে পুনরায় চালু করতে চান?",
"desktop.updater.installFailed.title": "আপডেট ব্যর্থ হয়েছে৷",
"desktop.updater.installFailed.message": "আপডেট ইনস্টল করতে ব্যর্থ হয়েছে",
"desktop.error.dev.rootNotFound":
"মূল উপাদান পাওয়া যায়নি. আপনি কি আপনার index.html এ যোগ করতে ভুলে গেছেন? অথবা হয়তো আইডি অ্যাট্রিবিউট ভুল বানান হয়েছে?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Verificar atualizações...",
"desktop.menu.reloadWebview": "Recarregar Webview",
"desktop.menu.restart": "Reiniciar",
"desktop.dialog.chooseFolder": "Escolher uma pasta",
"desktop.dialog.chooseFile": "Escolher um arquivo",
"desktop.dialog.saveFile": "Salvar arquivo",
"desktop.updater.checkFailed.title": "Falha ao verificar atualizações",
"desktop.updater.checkFailed.message": "Falha ao verificar atualizações",
"desktop.updater.none.title": "Nenhuma atualização disponível",
"desktop.updater.none.message": "Você já está usando a versão mais recente do OpenCode",
"desktop.updater.downloadFailed.title": "Falha na atualização",
"desktop.updater.downloadFailed.message": "Falha ao baixar a atualização",
"desktop.updater.downloaded.title": "Atualização baixada",
"desktop.updater.downloaded.prompt":
"A versão {{version}} do OpenCode foi baixada. Você gostaria de instalá-la e reiniciar?",
"desktop.updater.installFailed.title": "Falha na atualização",
"desktop.updater.installFailed.message": "Falha ao instalar a atualização",
"desktop.error.dev.rootNotFound":
"Elemento raiz não encontrado. Você esqueceu de adicioná-lo ao seu index.html? Ou talvez o atributo id foi escrito incorretamente?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Provjeri ažuriranja...",
"desktop.menu.reloadWebview": "Ponovo učitaj Webview",
"desktop.menu.restart": "Ponovo pokreni",
"desktop.dialog.chooseFolder": "Odaberi fasciklu",
"desktop.dialog.chooseFile": "Odaberi datoteku",
"desktop.dialog.saveFile": "Sačuvaj datoteku",
"desktop.updater.checkFailed.title": "Provjera ažuriranja nije uspjela",
"desktop.updater.checkFailed.message": "Nije moguće provjeriti ažuriranja",
"desktop.updater.none.title": "Nema dostupnog ažuriranja",
"desktop.updater.none.message": "Već koristiš najnoviju verziju OpenCode-a",
"desktop.updater.downloadFailed.title": "Ažuriranje nije uspjelo",
"desktop.updater.downloadFailed.message": "Neuspjelo preuzimanje ažuriranja",
"desktop.updater.downloaded.title": "Ažuriranje preuzeto",
"desktop.updater.downloaded.prompt":
"Verzija {{version}} OpenCode-a je preuzeta. Želiš li da je instaliraš i ponovo pokreneš aplikaciju?",
"desktop.updater.installFailed.title": "Ažuriranje nije uspjelo",
"desktop.updater.installFailed.message": "Neuspjela instalacija ažuriranja",
"desktop.error.dev.rootNotFound":
"Korijenski element nije pronađen. Da li si zaboravio da ga dodaš u index.html? Ili je možda id atribut pogrešno napisan?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Comproveu si hi ha actualitzacions...",
"desktop.menu.reloadWebview": "Torna a carregar Webview",
"desktop.menu.restart": "Reinicia",
"desktop.dialog.chooseFolder": "Trieu una carpeta",
"desktop.dialog.chooseFile": "Trieu un fitxer",
"desktop.dialog.saveFile": "Desa el fitxer",
"desktop.updater.checkFailed.title": "La comprovació d'actualització ha fallat",
"desktop.updater.checkFailed.message": "No s'ha pogut comprovar si hi ha actualitzacions",
"desktop.updater.none.title": "No hi ha cap actualització disponible",
"desktop.updater.none.message": "Ja utilitzeu la versió més recent d'OpenCode",
"desktop.updater.downloadFailed.title": "L'actualització ha fallat",
"desktop.updater.downloadFailed.message": "No s'ha pogut descarregar l'actualització",
"desktop.updater.downloaded.title": "Actualització baixada",
"desktop.updater.downloaded.prompt":
"S'ha baixat la versió {{version}} d'OpenCode. Voleu instal·lar-la i reiniciar l'aplicació?",
"desktop.updater.installFailed.title": "L'actualització ha fallat",
"desktop.updater.installFailed.message": "No s'ha pogut instal·lar l'actualització",
"desktop.error.dev.rootNotFound":
"No s'ha trobat l'element arrel. T'has oblidat d'afegir-lo al teu index.html? O potser l'atribut id s'ha escrit malament?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Zkontrolovat aktualizace...",
"desktop.menu.reloadWebview": "Znovu načíst Webview",
"desktop.menu.restart": "Restartovat",
"desktop.dialog.chooseFolder": "Vyberte složku",
"desktop.dialog.chooseFile": "Vyberte soubor",
"desktop.dialog.saveFile": "Uložit soubor",
"desktop.updater.checkFailed.title": "Kontrola aktualizace se nezdařila",
"desktop.updater.checkFailed.message": "Kontrola aktualizací se nezdařila",
"desktop.updater.none.title": "Není k dispozici žádná aktualizace",
"desktop.updater.none.message": "Již používáte nejnovější verzi OpenCode",
"desktop.updater.downloadFailed.title": "Aktualizace se nezdařila",
"desktop.updater.downloadFailed.message": "Stažení aktualizace se nezdařilo",
"desktop.updater.downloaded.title": "Aktualizace stažena",
"desktop.updater.downloaded.prompt":
"Byla stažena verze {{version}} aplikace OpenCode. Chcete ji nainstalovat a aplikaci znovu spustit?",
"desktop.updater.installFailed.title": "Aktualizace se nezdařila",
"desktop.updater.installFailed.message": "Aktualizaci se nepodařilo nainstalovat",
"desktop.error.dev.rootNotFound":
"Kořenový prvek nenalezen. Zapomněli jste to přidat do index.html? Nebo je možná chyba v atributu id?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Tjek for opdateringer...",
"desktop.menu.reloadWebview": "Genindlæs webvisning",
"desktop.menu.restart": "Genstart",
"desktop.dialog.chooseFolder": "Vælg en mappe",
"desktop.dialog.chooseFile": "Vælg en fil",
"desktop.dialog.saveFile": "Gem fil",
"desktop.updater.checkFailed.title": "Opdateringstjek mislykkedes",
"desktop.updater.checkFailed.message": "Kunne ikke tjekke for opdateringer",
"desktop.updater.none.title": "Ingen opdatering tilgængelig",
"desktop.updater.none.message": "Du bruger allerede den nyeste version af OpenCode",
"desktop.updater.downloadFailed.title": "Opdatering mislykkedes",
"desktop.updater.downloadFailed.message": "Kunne ikke downloade opdateringen",
"desktop.updater.downloaded.title": "Opdatering downloadet",
"desktop.updater.downloaded.prompt":
"Version {{version}} af OpenCode er blevet downloadet. Vil du installere den og genstarte?",
"desktop.updater.installFailed.title": "Opdatering mislykkedes",
"desktop.updater.installFailed.message": "Kunne ikke installere opdateringen",
"desktop.error.dev.rootNotFound":
"Rodelement ikke fundet. Har du glemt at tilføje det til din index.html? Eller måske er id-attributten stavet forkert?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Nach Updates suchen…",
"desktop.menu.reloadWebview": "Webview neu laden",
"desktop.menu.restart": "Neustart",
"desktop.dialog.chooseFolder": "Ordner auswählen",
"desktop.dialog.chooseFile": "Datei auswählen",
"desktop.dialog.saveFile": "Datei speichern",
"desktop.updater.checkFailed.title": "Updateprüfung fehlgeschlagen",
"desktop.updater.checkFailed.message": "Updates konnten nicht geprüft werden",
"desktop.updater.none.title": "Kein Update verfügbar",
"desktop.updater.none.message": "Sie verwenden bereits die neueste Version von OpenCode",
"desktop.updater.downloadFailed.title": "Update fehlgeschlagen",
"desktop.updater.downloadFailed.message": "Update konnte nicht heruntergeladen werden",
"desktop.updater.downloaded.title": "Update heruntergeladen",
"desktop.updater.downloaded.prompt":
"Version {{version}} von OpenCode wurde heruntergeladen. Möchten Sie sie installieren und neu starten?",
"desktop.updater.installFailed.title": "Update fehlgeschlagen",
"desktop.updater.installFailed.message": "Update konnte nicht installiert werden",
"desktop.error.dev.rootNotFound":
"Wurzelelement nicht gefunden. Haben Sie vergessen, es in Ihre index.html aufzunehmen? Oder wurde das ID-Attribut falsch geschrieben?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "އަޕްޑޭޓްސް އަށް ޗެކް ކޮށްލައްވާ...",
"desktop.menu.reloadWebview": "ވެބްވިއު ރީލޯޑް ކުރާށެވެ",
"desktop.menu.restart": "އަލުން ފަށާށެވެ",
"desktop.dialog.chooseFolder": "ފޯލްޑަރެއް ހޮވާށެވެ",
"desktop.dialog.chooseFile": "ފައިލެއް ހޮވާށެވެ",
"desktop.dialog.saveFile": "ފައިލް ސޭވްކުރުން",
"desktop.updater.checkFailed.title": "އަޕްޑޭޓް ޗެކް ފެއިލްވެއްޖެ",
"desktop.updater.checkFailed.message": "އަޕްޑޭޓްތައް ޗެކް ނުކުރެވުނެވެ",
"desktop.updater.none.title": "އެއްވެސް އަޕްޑޭޓެއް ނުލިބެއެވެ",
"desktop.updater.none.message": "މިހާރުވެސް ބޭނުން ކުރަމުންދަނީ OpenCode ގެ އެންމެ ފަހުގެ ވަރޝަން އެވެ",
"desktop.updater.downloadFailed.title": "އަޕްޑޭޓް ފެއިލްވެއްޖެ",
"desktop.updater.downloadFailed.message": "އަޕްޑޭޓް ޑައުންލޯޑް ނުކުރެވުނެވެ",
"desktop.updater.downloaded.title": "އަޕްޑޭޓް ޑައުންލޯޑް ކުރެވިއްޖެއެވެ",
"desktop.updater.downloaded.prompt":
"OpenCode ގެ ވަރޝަން {{version}} ޑައުންލޯޑް ކުރެވިއްޖެ، އިންސްޓޯލްކޮށް އަލުން ލޯންޗް ކުރަން ބޭނުން ހެއްޔެވެ؟",
"desktop.updater.installFailed.title": "އަޕްޑޭޓް ފެއިލްވެއްޖެ",
"desktop.updater.installFailed.message": "އަޕްޑޭޓް އިންސްޓޯލް ކުރަން ނާކާމިޔާބުވިއެވެ",
"desktop.error.dev.rootNotFound":
"ރޫޓް އެލިމެންޓް ނުފެނެއެވެ. ތިބާގެ index.html އަށް އެޑް ކުރަން ހަނދާން ނެތުނީ ހެއްޔެވެ؟ ނުވަތަ id އެޓްރިބިއުޓް ގޯސްކޮށް އިމްތިހާނު ވެދާނެ ހެއްޔެވެ؟",
}

View file

@ -1,22 +0,0 @@
export const dict: Record<string, string> = {
"desktop.menu.checkForUpdates": "དུས་མཐུན་ཚུ་གི་དོན་ལུ་ཞིབ་དཔྱད་འབད།",
"desktop.menu.reloadWebview": "ཡང་བསྐྱར་མངོན་གསལ་ Webview།",
"desktop.menu.restart": "ལོག་འགོ་བཙུགས།",
"desktop.dialog.chooseFolder": "སྣོད་འཛིན་ཅིག་གདམ་ཁ་རྐྱབས།",
"desktop.dialog.chooseFile": "ཡིག་སྣོད་གདམ་ཁ་རྐྱབས།",
"desktop.dialog.saveFile": "ཡིག་སྣོད་སྲུངས།",
"desktop.updater.checkFailed.title": "དུས་མཐུན་ཞིབ་དཔྱད་འཐུས་ཤོར་འབྱུང་ཡོདཔ།",
"desktop.updater.checkFailed.message": "དུས་མཐུན་བཟོ་ནིའི་དོན་ལུ་ ཞིབ་དཔྱད་འབད་ནི་ལུ་འཐུས་ཤོར་འབྱུང་ཡོདཔ།",
"desktop.updater.none.title": "དུས་མཐུན་བཟོ་མི་ཚུགས།",
"desktop.updater.none.message": "ཁྱོད་ཀྱིས་ཧེ་མ་ལས་ OpenCodeགི་ཐོན་རིམ་གསརཔ་འདི་ལག་ལེན་འཐབ་དོ།",
"desktop.updater.downloadFailed.title": "དུས་མཐུན་འཐུས་ཤོར་འབྱུང་ཡོདཔ།",
"desktop.updater.downloadFailed.message": "དུས་མཐུན་ཕབ་ལེན་འབད་ནི་ལུ་འཐུས་ཤོར་འབྱུང་ཡོདཔ།",
"desktop.updater.downloaded.title": "དུས་མཐུན་ཕབ་ལེན་འབད་ཡོདཔ།",
"desktop.updater.downloaded.prompt":
"OpenCode གི་ཐོན་རིམ་ {{version}} ཕབ་ལེན་འབད་ཡོདཔ་ལས་ གཞི་བཙུགས་འབད་དེ་ ལོག་འགོ་བཙུགས་ནི་ཨིན་ན?",
"desktop.updater.installFailed.title": "དུས་མཐུན་འཐུས་ཤོར་འབྱུང་ཡོདཔ།",
"desktop.updater.installFailed.message": "དུས་མཐུན་གཞི་བཙུགས་འབད་ནི་ལུ་འཐུས་ཤོར་འབྱུང་ཡོདཔ།",
"desktop.error.dev.rootNotFound":
"རྩ་བའི་ཆ་ཤས་འཚོལ་མ་ཐོབ། ཁྱོད་ཀྱི་ index.html ལུ་ཁ་སྐོང་འབད་ནི་བརྗེད་སོང་ག? ཡང་ན་ id ཁྱད་ཆོས་འདི་ཡིག་སྡེབ་འཛོལ་བ་འོང་ག?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Έλεγχος για ενημερώσεις...",
"desktop.menu.reloadWebview": "Επανάληψη φόρτωσης Webview",
"desktop.menu.restart": "Επανεκκίνηση",
"desktop.dialog.chooseFolder": "Επιλογή φακέλου",
"desktop.dialog.chooseFile": "Επιλογή αρχείου",
"desktop.dialog.saveFile": "Αποθήκευση αρχείου",
"desktop.updater.checkFailed.title": "Ο έλεγχος ενημέρωσης απέτυχε",
"desktop.updater.checkFailed.message": "Απέτυχε ο έλεγχος για ενημερώσεις",
"desktop.updater.none.title": "Δεν υπάρχει διαθέσιμη ενημέρωση",
"desktop.updater.none.message": "Χρησιμοποιείτε ήδη την πιο πρόσφατη έκδοση του OpenCode",
"desktop.updater.downloadFailed.title": "Η ενημέρωση απέτυχε",
"desktop.updater.downloadFailed.message": "Απέτυχε η λήψη της ενημέρωσης",
"desktop.updater.downloaded.title": "Η ενημέρωση λήφθηκε",
"desktop.updater.downloaded.prompt":
"Έχει γίνει λήψη της έκδοσης {{version}} του OpenCode. Θέλετε να την εγκαταστήσετε και να επανεκκινήσετε την εφαρμογή;",
"desktop.updater.installFailed.title": "Η ενημέρωση απέτυχε",
"desktop.updater.installFailed.message": "Αποτυχία εγκατάστασης ενημέρωσης",
"desktop.error.dev.rootNotFound":
"Το στοιχείο ρίζας δεν βρέθηκε. Ξεχάσατε να το προσθέσετε στο index.html; Ή μήπως το χαρακτηριστικό id γράφτηκε λάθος;",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Check for Updates...",
"desktop.menu.reloadWebview": "Reload Webview",
"desktop.menu.restart": "Restart",
"desktop.dialog.chooseFolder": "Choose a folder",
"desktop.dialog.chooseFile": "Choose a file",
"desktop.dialog.saveFile": "Save file",
"desktop.updater.checkFailed.title": "Update Check Failed",
"desktop.updater.checkFailed.message": "Failed to check for updates",
"desktop.updater.none.title": "No Update Available",
"desktop.updater.none.message": "You are already using the latest version of OpenCode",
"desktop.updater.downloadFailed.title": "Update Failed",
"desktop.updater.downloadFailed.message": "Failed to download update",
"desktop.updater.downloaded.title": "Update Downloaded",
"desktop.updater.downloaded.prompt":
"Version {{version}} of OpenCode has been downloaded, would you like to install it and relaunch?",
"desktop.updater.installFailed.title": "Update Failed",
"desktop.updater.installFailed.message": "Failed to install update",
"desktop.error.dev.rootNotFound":
"Root element not found. Did you forget to add it to your index.html? Or maybe the id attribute got misspelled?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Buscar actualizaciones...",
"desktop.menu.reloadWebview": "Recargar vista web",
"desktop.menu.restart": "Reiniciar",
"desktop.dialog.chooseFolder": "Elegir una carpeta",
"desktop.dialog.chooseFile": "Elegir un archivo",
"desktop.dialog.saveFile": "Guardar archivo",
"desktop.updater.checkFailed.title": "Comprobación de actualizaciones fallida",
"desktop.updater.checkFailed.message": "No se pudieron buscar actualizaciones",
"desktop.updater.none.title": "No hay actualizaciones disponibles",
"desktop.updater.none.message": "Ya estás usando la versión más reciente de OpenCode",
"desktop.updater.downloadFailed.title": "Actualización fallida",
"desktop.updater.downloadFailed.message": "No se pudo descargar la actualización",
"desktop.updater.downloaded.title": "Actualización descargada",
"desktop.updater.downloaded.prompt":
"Se ha descargado la versión {{version}} de OpenCode. ¿Quieres instalarla y reiniciar?",
"desktop.updater.installFailed.title": "Actualización fallida",
"desktop.updater.installFailed.message": "No se pudo instalar la actualización",
"desktop.error.dev.rootNotFound":
"Elemento raíz no encontrado. ¿Olvidaste añadirlo a tu index.html? ¿O tal vez el atributo id está mal escrito?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Kontrolli värskendusi...",
"desktop.menu.reloadWebview": "Laadi veebivaade uuesti",
"desktop.menu.restart": "Taaskäivita",
"desktop.dialog.chooseFolder": "Valige kaust",
"desktop.dialog.chooseFile": "Valige fail",
"desktop.dialog.saveFile": "Salvesta fail",
"desktop.updater.checkFailed.title": "Värskenduskontroll ebaõnnestus",
"desktop.updater.checkFailed.message": "Värskenduste kontrollimine ebaõnnestus",
"desktop.updater.none.title": "Värskendus pole saadaval",
"desktop.updater.none.message": "Kasutate juba rakenduse OpenCode uusimat versiooni",
"desktop.updater.downloadFailed.title": "Värskendus ebaõnnestus",
"desktop.updater.downloadFailed.message": "Värskenduse allalaadimine ebaõnnestus",
"desktop.updater.downloaded.title": "Värskendus alla laaditud",
"desktop.updater.downloaded.prompt":
"OpenCode'i versioon {{version}} on alla laaditud. Kas soovite selle installida ja rakenduse taaskäivitada?",
"desktop.updater.installFailed.title": "Värskendus ebaõnnestus",
"desktop.updater.installFailed.message": "Värskenduse installimine ebaõnnestus",
"desktop.error.dev.rootNotFound":
"Juurelementi ei leitud. Kas unustasite selle lisada oma loendisse index.html? Või äkki on id-atribuut valesti kirjutatud?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "بررسی به روز رسانی...",
"desktop.menu.reloadWebview": "بارگذاری مجدد Webview",
"desktop.menu.restart": "راه اندازی مجدد",
"desktop.dialog.chooseFolder": "یک پوشه را انتخاب کنید",
"desktop.dialog.chooseFile": "یک فایل را انتخاب کنید",
"desktop.dialog.saveFile": "ذخیره فایل",
"desktop.updater.checkFailed.title": "بررسی به‌روزرسانی انجام نشد",
"desktop.updater.checkFailed.message": "بررسی به‌روزرسانی‌ها انجام نشد",
"desktop.updater.none.title": "به روز رسانی موجود نیست",
"desktop.updater.none.message": "شما در حال حاضر از آخرین نسخه OpenCode استفاده می کنید",
"desktop.updater.downloadFailed.title": "به روز رسانی انجام نشد",
"desktop.updater.downloadFailed.message": "به روز رسانی دانلود نشد",
"desktop.updater.downloaded.title": "به روز رسانی دانلود شد",
"desktop.updater.downloaded.prompt":
"نسخه {{version}} OpenCode دانلود شده است، آیا می خواهید آن را نصب کنید و دوباره راه اندازی کنید؟",
"desktop.updater.installFailed.title": "به روز رسانی انجام نشد",
"desktop.updater.installFailed.message": "به روز رسانی نصب نشد",
"desktop.error.dev.rootNotFound":
"عنصر ریشه یافت نشد. آیا فراموش کرده اید که آن را به index.html خود اضافه کنید؟ یا شاید ویژگی id اشتباه املایی داشته باشد؟",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Tarkista päivitykset...",
"desktop.menu.reloadWebview": "Lataa verkkonäkymä uudelleen",
"desktop.menu.restart": "Käynnistä uudelleen",
"desktop.dialog.chooseFolder": "Valitse kansio",
"desktop.dialog.chooseFile": "Valitse tiedosto",
"desktop.dialog.saveFile": "Tallenna tiedosto",
"desktop.updater.checkFailed.title": "Päivitystarkistus epäonnistui",
"desktop.updater.checkFailed.message": "Päivitysten tarkistaminen epäonnistui",
"desktop.updater.none.title": "Päivitystä ei ole saatavilla",
"desktop.updater.none.message": "Käytät jo OpenCoden uusinta versiota",
"desktop.updater.downloadFailed.title": "Päivitys epäonnistui",
"desktop.updater.downloadFailed.message": "Päivityksen lataaminen epäonnistui",
"desktop.updater.downloaded.title": "Päivitys ladattu",
"desktop.updater.downloaded.prompt":
"OpenCoden versio {{version}} on ladattu. Haluatko asentaa sen ja käynnistää OpenCoden uudelleen?",
"desktop.updater.installFailed.title": "Päivitys epäonnistui",
"desktop.updater.installFailed.message": "Päivityksen asentaminen epäonnistui",
"desktop.error.dev.rootNotFound":
"Juurielementtiä ei löydy. Unohditko lisätä sen index.html-tiedostoosi? Tai ehkä id-attribuutti on kirjoitettu väärin?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Kanna fyri dagføringum...",
"desktop.menu.reloadWebview": "Endurlesa vevvísing",
"desktop.menu.restart": "Endurbyrja",
"desktop.dialog.chooseFolder": "Vel eina mappu",
"desktop.dialog.chooseFile": "Vel eina fílu",
"desktop.dialog.saveFile": "Goym fílu",
"desktop.updater.checkFailed.title": "Dagføringarkanningin miseydnaðist",
"desktop.updater.checkFailed.message": "Tað eydnaðist ikki at kanna fyri dagføringum",
"desktop.updater.none.title": "Eingin dagføring er tøk",
"desktop.updater.none.message": "Tú brúkar longu nýggjastu útgávuna av OpenCode.",
"desktop.updater.downloadFailed.title": "Dagføring miseydnaðist",
"desktop.updater.downloadFailed.message": "Tað eydnaðist ikki at heinta dagføring",
"desktop.updater.downloaded.title": "Dagføring heintað",
"desktop.updater.downloaded.prompt":
"Útgáva {{version}} av OpenCode er heintað, vilt tú seta hana upp og seta hana í gongd aftur?",
"desktop.updater.installFailed.title": "Dagføring miseydnaðist",
"desktop.updater.installFailed.message": "Tað eydnaðist ikki at seta upp dagføring",
"desktop.error.dev.rootNotFound":
"Rótarevni ikki funnið. Gloymdi tú at leggja tað til títt index.html? Ella kanska fekk id eginleikin skeivt stavað?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Rechercher des mises à jour...",
"desktop.menu.reloadWebview": "Recharger la vue Web",
"desktop.menu.restart": "Redémarrer",
"desktop.dialog.chooseFolder": "Choisir un dossier",
"desktop.dialog.chooseFile": "Choisir un fichier",
"desktop.dialog.saveFile": "Enregistrer le fichier",
"desktop.updater.checkFailed.title": "Échec de la vérification des mises à jour",
"desktop.updater.checkFailed.message": "Impossible de vérifier les mises à jour",
"desktop.updater.none.title": "Aucune mise à jour disponible",
"desktop.updater.none.message": "Vous utilisez déjà la dernière version d'OpenCode",
"desktop.updater.downloadFailed.title": "Échec de la mise à jour",
"desktop.updater.downloadFailed.message": "Impossible de télécharger la mise à jour",
"desktop.updater.downloaded.title": "Mise à jour téléchargée",
"desktop.updater.downloaded.prompt":
"La version {{version}} d'OpenCode a été téléchargée. Voulez-vous l'installer et relancer l'application ?",
"desktop.updater.installFailed.title": "Échec de la mise à jour",
"desktop.updater.installFailed.message": "Impossible d'installer la mise à jour",
"desktop.error.dev.rootNotFound":
"Élément racine introuvable. Avez-vous oublié de l'ajouter à votre index.html ? Ou peut-être que l'attribut id est mal orthographié ?",
}

View file

@ -1,20 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "בדוק אם יש עדכונים...",
"desktop.menu.reloadWebview": "טען מחדש את תצוגת האינטרנט",
"desktop.menu.restart": "הפעל מחדש",
"desktop.dialog.chooseFolder": "בחירת תיקייה",
"desktop.dialog.chooseFile": "בחירת קובץ",
"desktop.dialog.saveFile": "שמירת קובץ",
"desktop.updater.checkFailed.title": "בדיקת העדכונים נכשלה",
"desktop.updater.checkFailed.message": "לא ניתן לבדוק אם קיימים עדכונים",
"desktop.updater.none.title": "אין עדכון זמין",
"desktop.updater.none.message": "כבר מותקנת הגרסה העדכנית ביותר של OpenCode",
"desktop.updater.downloadFailed.title": "העדכון נכשל",
"desktop.updater.downloadFailed.message": "הורדת העדכון נכשלה",
"desktop.updater.downloaded.title": "העדכון הורד",
"desktop.updater.downloaded.prompt": "גרסה {{version}} של OpenCode הורדה. להתקין אותה ולהפעיל מחדש את היישום?",
"desktop.updater.installFailed.title": "העדכון נכשל",
"desktop.updater.installFailed.message": "התקנת העדכון נכשלה",
"desktop.error.dev.rootNotFound":
"רכיב השורש לא נמצא. האם שכחת להוסיף אותו ל-index.html, או שיש טעות בשם מאפיין ה-id?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "अद्यतन के लिए जाँच...",
"desktop.menu.reloadWebview": "Webview पुनः लोड करें",
"desktop.menu.restart": "पुनः आरंभ करें",
"desktop.dialog.chooseFolder": "एक फ़ोल्डर चुनें",
"desktop.dialog.chooseFile": "एक फ़ाइल चुनें",
"desktop.dialog.saveFile": "फ़ाइल सहेजें",
"desktop.updater.checkFailed.title": "अद्यतन जांच विफल",
"desktop.updater.checkFailed.message": "अद्यतनों की जाँच करने में विफल",
"desktop.updater.none.title": "कोई अपडेट उपलब्ध नहीं",
"desktop.updater.none.message": "आप पहले से ही OpenCode का नवीनतम संस्करण उपयोग कर रहे हैं",
"desktop.updater.downloadFailed.title": "अपडेट विफल",
"desktop.updater.downloadFailed.message": "अद्यतन डाउनलोड करने में विफल",
"desktop.updater.downloaded.title": "अद्यतन डाउनलोड किया गया",
"desktop.updater.downloaded.prompt":
"OpenCode का संस्करण {{version}} डाउनलोड हो गया है। क्या आप इसे इंस्टॉल करके ऐप को फिर से खोलना चाहेंगे?",
"desktop.updater.installFailed.title": "अपडेट विफल",
"desktop.updater.installFailed.message": "अद्यतन स्थापित करने में विफल",
"desktop.error.dev.rootNotFound":
"मूल तत्व नहीं मिला. क्या आप इसे अपने index.html में जोड़ना भूल गए? या हो सकता है कि आईडी विशेषता गलत वर्तनी हो गई हो?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Provjeri ažuriranja...",
"desktop.menu.reloadWebview": "Ponovno učitaj Webview",
"desktop.menu.restart": "Ponovno pokreni",
"desktop.dialog.chooseFolder": "Odaberite mapu",
"desktop.dialog.chooseFile": "Odaberite datoteku",
"desktop.dialog.saveFile": "Spremi datoteku",
"desktop.updater.checkFailed.title": "Provjera ažuriranja nije uspjela",
"desktop.updater.checkFailed.message": "Provjera ažuriranja nije uspjela",
"desktop.updater.none.title": "Nema dostupnih ažuriranja",
"desktop.updater.none.message": "Već koristite najnoviju verziju OpenCode",
"desktop.updater.downloadFailed.title": "Ažuriranje nije uspjelo",
"desktop.updater.downloadFailed.message": "Preuzimanje ažuriranja nije uspjelo",
"desktop.updater.downloaded.title": "Ažuriranje preuzeto",
"desktop.updater.downloaded.prompt":
"Preuzeta je verzija {{version}} aplikacije OpenCode. Želite li je instalirati i ponovno pokrenuti aplikaciju?",
"desktop.updater.installFailed.title": "Ažuriranje nije uspjelo",
"desktop.updater.installFailed.message": "Instalacija ažuriranja nije uspjela",
"desktop.error.dev.rootNotFound":
"Korijenski element nije pronađen. Jeste li ga zaboravili dodati u svoj index.html? Ili je možda atribut id-a pogrešno napisan?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Frissítések keresése...",
"desktop.menu.reloadWebview": "A Webview újratöltése",
"desktop.menu.restart": "Újraindítás",
"desktop.dialog.chooseFolder": "Válasszon egy mappát",
"desktop.dialog.chooseFile": "Válasszon egy fájlt",
"desktop.dialog.saveFile": "Fájl mentése",
"desktop.updater.checkFailed.title": "Frissítés ellenőrzése sikertelen",
"desktop.updater.checkFailed.message": "Nem sikerült ellenőrizni a frissítéseket",
"desktop.updater.none.title": "Nem érhető el frissítés",
"desktop.updater.none.message": "Már az OpenCode legújabb verzióját használja",
"desktop.updater.downloadFailed.title": "Frissítés sikertelen",
"desktop.updater.downloadFailed.message": "Nem sikerült letölteni a frissítést",
"desktop.updater.downloaded.title": "Frissítés letöltve",
"desktop.updater.downloaded.prompt":
"Az OpenCode {{version}} verziója letöltődött. Szeretné telepíteni és újraindítani az alkalmazást?",
"desktop.updater.installFailed.title": "Frissítés sikertelen",
"desktop.updater.installFailed.message": "Nem sikerült telepíteni a frissítést",
"desktop.error.dev.rootNotFound":
"A gyökérelem nem található. Elfelejtette hozzáadni az index.html-hez? Vagy lehet, hogy az id attribútumot rosszul írták?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Ստուգեք թարմացումների համար...",
"desktop.menu.reloadWebview": "Վերբեռնել Webview",
"desktop.menu.restart": "Վերագործարկեք",
"desktop.dialog.chooseFolder": "Ընտրեք թղթապանակ",
"desktop.dialog.chooseFile": "Ընտրեք ֆայլ",
"desktop.dialog.saveFile": "Պահպանել ֆայլը",
"desktop.updater.checkFailed.title": "Թարմացման ստուգումը ձախողվեց",
"desktop.updater.checkFailed.message": "Չհաջողվեց ստուգել թարմացումների առկայությունը",
"desktop.updater.none.title": "Թարմացում չկա",
"desktop.updater.none.message": "Դուք արդեն օգտագործում եք OpenCode-ի վերջին տարբերակը",
"desktop.updater.downloadFailed.title": "Թարմացումը ձախողվեց",
"desktop.updater.downloadFailed.message": "Չհաջողվեց ներբեռնել թարմացումը",
"desktop.updater.downloaded.title": "Թարմացումը ներբեռնված է",
"desktop.updater.downloaded.prompt":
"OpenCode-ի {{version}} տարբերակը ներբեռնվել է: Ցանկանու՞մ եք տեղադրել այն և վերագործարկել:",
"desktop.updater.installFailed.title": "Թարմացումը ձախողվեց",
"desktop.updater.installFailed.message": "Չհաջողվեց տեղադրել թարմացումը",
"desktop.error.dev.rootNotFound":
"Արմատային տարրը չի գտնվել։ Մոռացե՞լ եք այն ավելացնել ձեր index.html-ում: Կամ գուցե id հատկանիշը սխալ է գրվել:",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Periksa pembaruan...",
"desktop.menu.reloadWebview": "Muat ulang WebView",
"desktop.menu.restart": "Mulai ulang",
"desktop.dialog.chooseFolder": "Pilih folder",
"desktop.dialog.chooseFile": "Pilih berkas",
"desktop.dialog.saveFile": "Simpan berkas",
"desktop.updater.checkFailed.title": "Pemeriksaan pembaruan gagal",
"desktop.updater.checkFailed.message": "Gagal memeriksa pembaruan",
"desktop.updater.none.title": "Tidak ada pembaruan",
"desktop.updater.none.message": "Anda sudah menggunakan versi terbaru OpenCode",
"desktop.updater.downloadFailed.title": "Pembaruan gagal",
"desktop.updater.downloadFailed.message": "Gagal mengunduh pembaruan",
"desktop.updater.downloaded.title": "Pembaruan diunduh",
"desktop.updater.downloaded.prompt":
"OpenCode versi {{version}} telah diunduh. Apakah Anda ingin menginstalnya dan menjalankan ulang aplikasi?",
"desktop.updater.installFailed.title": "Pembaruan gagal",
"desktop.updater.installFailed.message": "Gagal menginstal pembaruan",
"desktop.error.dev.rootNotFound":
"Elemen root tidak ditemukan. Apakah Anda lupa menambahkannya ke index.html? Atau mungkin atribut id salah eja?",
}

View file

@ -1,213 +0,0 @@
import * as i18n from "@solid-primitives/i18n"
import {
DESKTOP_NATIVE_LOCALES,
detectDesktopNativeLocale,
type DesktopNativeLocale,
} from "../../../../app/src/i18n/desktop-native"
import { dict as desktopEn } from "./en"
import { dict as desktopZh } from "./zh"
import { dict as desktopZht } from "./zht"
import { dict as desktopKo } from "./ko"
import { dict as desktopDe } from "./de"
import { dict as desktopEs } from "./es"
import { dict as desktopFr } from "./fr"
import { dict as desktopDa } from "./da"
import { dict as desktopJa } from "./ja"
import { dict as desktopPl } from "./pl"
import { dict as desktopRu } from "./ru"
import { dict as desktopUk } from "./uk"
import { dict as desktopAr } from "./ar"
import { dict as desktopHe } from "./he"
import { dict as desktopNo } from "./no"
import { dict as desktopBr } from "./br"
import { dict as desktopBs } from "./bs"
import { dict as desktopTr } from "./tr"
import { dict as desktopHi } from "./hi"
import { dict as desktopNl } from "./nl"
import { dict as desktopId } from "./id"
import { dict as desktopVi } from "./vi"
import { dict as desktopIt } from "./it"
import { dict as desktopUr } from "./ur"
import { dict as desktopPa } from "./pa"
import { dict as desktopAz } from "./az"
import { dict as desktopFi } from "./fi"
import { dict as desktopSv } from "./sv"
import { dict as desktopTh } from "./th"
import { dict as desktopAm } from "./am"
import { dict as desktopBg } from "./bg"
import { dict as desktopBn } from "./bn"
import { dict as desktopCa } from "./ca"
import { dict as desktopCs } from "./cs"
import { dict as desktopDv } from "./dv"
import { dict as desktopDz } from "./dz"
import { dict as desktopEl } from "./el"
import { dict as desktopEt } from "./et"
import { dict as desktopFa } from "./fa"
import { dict as desktopFo } from "./fo"
import { dict as desktopHr } from "./hr"
import { dict as desktopHu } from "./hu"
import { dict as desktopHy } from "./hy"
import { dict as desktopIs } from "./is"
import { dict as desktopKa } from "./ka"
import { dict as desktopKm } from "./km"
import { dict as desktopLo } from "./lo"
import { dict as desktopLt } from "./lt"
import { dict as desktopLv } from "./lv"
import { dict as desktopMk } from "./mk"
import { dict as desktopMn } from "./mn"
import { dict as desktopMs } from "./ms"
import { dict as desktopMy } from "./my"
import { dict as desktopNe } from "./ne"
import { dict as desktopRo } from "./ro"
import { dict as desktopSi } from "./si"
import { dict as desktopSk } from "./sk"
import { dict as desktopSl } from "./sl"
import { dict as desktopSq } from "./sq"
import { dict as desktopSr } from "./sr"
import { dict as desktopTg } from "./tg"
import { dict as desktopTk } from "./tk"
import { dict as desktopUz } from "./uz"
export type Locale = DesktopNativeLocale
type RawDictionary = typeof desktopEn
type Dictionary = Record<keyof i18n.Flatten<RawDictionary>, string>
function detectLocale(): Locale {
if (typeof navigator !== "object") return "en"
return detectDesktopNativeLocale(navigator.languages?.length ? navigator.languages : [navigator.language])
}
function parseLocale(value: unknown): Locale | null {
if (!value) return null
if (typeof value !== "string") return null
if ((DESKTOP_NATIVE_LOCALES as readonly string[]).includes(value)) return value as Locale
return null
}
function parseRecord(value: unknown) {
if (!value || typeof value !== "object") return null
if (Array.isArray(value)) return null
return value as Record<string, unknown>
}
function parseStored(value: unknown) {
if (typeof value !== "string") return value
try {
return JSON.parse(value) as unknown
} catch {
return value
}
}
function pickLocale(value: unknown): Locale | null {
const direct = parseLocale(value)
if (direct) return direct
const record = parseRecord(value)
if (!record) return null
return parseLocale(record.locale)
}
const base = i18n.flatten(desktopEn)
function build(locale: Locale): Dictionary {
if (locale === "en") return base
if (locale === "zh") return { ...base, ...i18n.flatten(desktopZh) }
if (locale === "zht") return { ...base, ...i18n.flatten(desktopZht) }
if (locale === "de") return { ...base, ...i18n.flatten(desktopDe) }
if (locale === "es") return { ...base, ...i18n.flatten(desktopEs) }
if (locale === "fr") return { ...base, ...i18n.flatten(desktopFr) }
if (locale === "da") return { ...base, ...i18n.flatten(desktopDa) }
if (locale === "ja") return { ...base, ...i18n.flatten(desktopJa) }
if (locale === "pl") return { ...base, ...i18n.flatten(desktopPl) }
if (locale === "ru") return { ...base, ...i18n.flatten(desktopRu) }
if (locale === "uk") return { ...base, ...i18n.flatten(desktopUk) }
if (locale === "ar") return { ...base, ...i18n.flatten(desktopAr) }
if (locale === "he") return { ...base, ...i18n.flatten(desktopHe) }
if (locale === "no") return { ...base, ...i18n.flatten(desktopNo) }
if (locale === "br") return { ...base, ...i18n.flatten(desktopBr) }
if (locale === "bs") return { ...base, ...i18n.flatten(desktopBs) }
if (locale === "tr") return { ...base, ...i18n.flatten(desktopTr) }
if (locale === "hi") return { ...base, ...i18n.flatten(desktopHi) }
if (locale === "nl") return { ...base, ...i18n.flatten(desktopNl) }
if (locale === "id") return { ...base, ...i18n.flatten(desktopId) }
if (locale === "vi") return { ...base, ...i18n.flatten(desktopVi) }
if (locale === "it") return { ...base, ...i18n.flatten(desktopIt) }
if (locale === "ur") return { ...base, ...i18n.flatten(desktopUr) }
if (locale === "pa") return { ...base, ...i18n.flatten(desktopPa) }
if (locale === "az") return { ...base, ...i18n.flatten(desktopAz) }
if (locale === "fi") return { ...base, ...i18n.flatten(desktopFi) }
if (locale === "sv") return { ...base, ...i18n.flatten(desktopSv) }
if (locale === "th") return { ...base, ...i18n.flatten(desktopTh) }
if (locale === "am") return { ...base, ...i18n.flatten(desktopAm) }
if (locale === "bg") return { ...base, ...i18n.flatten(desktopBg) }
if (locale === "bn") return { ...base, ...i18n.flatten(desktopBn) }
if (locale === "ca") return { ...base, ...i18n.flatten(desktopCa) }
if (locale === "cs") return { ...base, ...i18n.flatten(desktopCs) }
if (locale === "dv") return { ...base, ...i18n.flatten(desktopDv) }
if (locale === "dz") return { ...base, ...i18n.flatten(desktopDz) }
if (locale === "el") return { ...base, ...i18n.flatten(desktopEl) }
if (locale === "et") return { ...base, ...i18n.flatten(desktopEt) }
if (locale === "fa") return { ...base, ...i18n.flatten(desktopFa) }
if (locale === "fo") return { ...base, ...i18n.flatten(desktopFo) }
if (locale === "hr") return { ...base, ...i18n.flatten(desktopHr) }
if (locale === "hu") return { ...base, ...i18n.flatten(desktopHu) }
if (locale === "hy") return { ...base, ...i18n.flatten(desktopHy) }
if (locale === "is") return { ...base, ...i18n.flatten(desktopIs) }
if (locale === "ka") return { ...base, ...i18n.flatten(desktopKa) }
if (locale === "km") return { ...base, ...i18n.flatten(desktopKm) }
if (locale === "lo") return { ...base, ...i18n.flatten(desktopLo) }
if (locale === "lt") return { ...base, ...i18n.flatten(desktopLt) }
if (locale === "lv") return { ...base, ...i18n.flatten(desktopLv) }
if (locale === "mk") return { ...base, ...i18n.flatten(desktopMk) }
if (locale === "mn") return { ...base, ...i18n.flatten(desktopMn) }
if (locale === "ms") return { ...base, ...i18n.flatten(desktopMs) }
if (locale === "my") return { ...base, ...i18n.flatten(desktopMy) }
if (locale === "ne") return { ...base, ...i18n.flatten(desktopNe) }
if (locale === "ro") return { ...base, ...i18n.flatten(desktopRo) }
if (locale === "si") return { ...base, ...i18n.flatten(desktopSi) }
if (locale === "sk") return { ...base, ...i18n.flatten(desktopSk) }
if (locale === "sl") return { ...base, ...i18n.flatten(desktopSl) }
if (locale === "sq") return { ...base, ...i18n.flatten(desktopSq) }
if (locale === "sr") return { ...base, ...i18n.flatten(desktopSr) }
if (locale === "tg") return { ...base, ...i18n.flatten(desktopTg) }
if (locale === "tk") return { ...base, ...i18n.flatten(desktopTk) }
if (locale === "uz") return { ...base, ...i18n.flatten(desktopUz) }
return { ...base, ...i18n.flatten(desktopKo) }
}
const state = {
locale: detectLocale(),
dict: base as Dictionary,
init: undefined as Promise<Locale> | undefined,
}
state.dict = build(state.locale)
const translate = i18n.translator(() => state.dict, i18n.resolveTemplate)
export function t(key: keyof Dictionary, params?: Record<string, string | number>) {
return translate(key, params)
}
export function initI18n(): Promise<Locale> {
const cached = state.init
if (cached) return cached
const promise = (async () => {
const raw = await window.api.storeGet("opencode.global.dat", "language").catch(() => null)
const value = parseStored(raw)
const next = pickLocale(value) ?? state.locale
state.locale = next
state.dict = build(next)
return next
})().catch(() => state.locale)
state.init = promise
return promise
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Leita að uppfærslum...",
"desktop.menu.reloadWebview": "Endurhlaða Webview",
"desktop.menu.restart": "Endurræsa",
"desktop.dialog.chooseFolder": "Veldu möppu",
"desktop.dialog.chooseFile": "Veldu skrá",
"desktop.dialog.saveFile": "Vista skrá",
"desktop.updater.checkFailed.title": "Uppfærsluathugun mistókst",
"desktop.updater.checkFailed.message": "Mistókst að leita að uppfærslum",
"desktop.updater.none.title": "Engin uppfærsla í boði",
"desktop.updater.none.message": "Þú ert nú þegar að nota nýjustu útgáfuna af OpenCode",
"desktop.updater.downloadFailed.title": "Uppfærsla mistókst",
"desktop.updater.downloadFailed.message": "Mistókst að hlaða niður uppfærslu",
"desktop.updater.downloaded.title": "Uppfærslu hlaðið niður",
"desktop.updater.downloaded.prompt":
"Útgáfu {{version}} af OpenCode hefur verið hlaðið niður. Viltu setja hana upp og endurræsa?",
"desktop.updater.installFailed.title": "Uppfærsla mistókst",
"desktop.updater.installFailed.message": "Mistókst að setja upp uppfærslu",
"desktop.error.dev.rootNotFound":
"Rótarþáttur fannst ekki. Gleymdirðu að bæta því við index.html þinn? Eða er id eigindin kannski rangt stafsett?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Controlla gli aggiornamenti...",
"desktop.menu.reloadWebview": "Ricarica la webview",
"desktop.menu.restart": "Riavvia",
"desktop.dialog.chooseFolder": "Scegli una cartella",
"desktop.dialog.chooseFile": "Scegli un file",
"desktop.dialog.saveFile": "Salva file",
"desktop.updater.checkFailed.title": "Controllo degli aggiornamenti non riuscito",
"desktop.updater.checkFailed.message": "Impossibile controllare gli aggiornamenti",
"desktop.updater.none.title": "Nessun aggiornamento disponibile",
"desktop.updater.none.message": "Stai già utilizzando l'ultima versione di OpenCode",
"desktop.updater.downloadFailed.title": "Aggiornamento non riuscito",
"desktop.updater.downloadFailed.message": "Impossibile scaricare l'aggiornamento",
"desktop.updater.downloaded.title": "Aggiornamento scaricato",
"desktop.updater.downloaded.prompt":
"La versione {{version}} di OpenCode è stata scaricata. Vuoi installarla e riavviare l'app?",
"desktop.updater.installFailed.title": "Aggiornamento non riuscito",
"desktop.updater.installFailed.message": "Impossibile installare l'aggiornamento",
"desktop.error.dev.rootNotFound":
"Elemento radice non trovato. Hai dimenticato di aggiungerlo al tuo index.html? O forse l'attributo id è stato scritto in modo errato?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "アップデートを確認...",
"desktop.menu.reloadWebview": "Webview を再読み込み",
"desktop.menu.restart": "再起動",
"desktop.dialog.chooseFolder": "フォルダーを選択",
"desktop.dialog.chooseFile": "ファイルを選択",
"desktop.dialog.saveFile": "ファイルを保存",
"desktop.updater.checkFailed.title": "アップデートの確認に失敗しました",
"desktop.updater.checkFailed.message": "アップデートを確認できませんでした",
"desktop.updater.none.title": "利用可能なアップデートはありません",
"desktop.updater.none.message": "すでに最新バージョンの OpenCode を使用しています",
"desktop.updater.downloadFailed.title": "アップデートに失敗しました",
"desktop.updater.downloadFailed.message": "アップデートをダウンロードできませんでした",
"desktop.updater.downloaded.title": "アップデートをダウンロードしました",
"desktop.updater.downloaded.prompt":
"OpenCode のバージョン {{version}} がダウンロードされました。インストールして再起動しますか?",
"desktop.updater.installFailed.title": "アップデートに失敗しました",
"desktop.updater.installFailed.message": "アップデートをインストールできませんでした",
"desktop.error.dev.rootNotFound":
"ルート要素が見つかりません。index.htmlに追加するのを忘れていませんかまたはid属性のスペルが間違っていませんか",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "შეამოწმეთ განახლებები...",
"desktop.menu.reloadWebview": "გადატვირთვა Webview",
"desktop.menu.restart": "გადატვირთვა",
"desktop.dialog.chooseFolder": "აირჩიე საქაღალდე",
"desktop.dialog.chooseFile": "აირჩიე ფაილი",
"desktop.dialog.saveFile": "ფაილის შენახვა",
"desktop.updater.checkFailed.title": "განახლების შემოწმება ვერ მოხერხდა",
"desktop.updater.checkFailed.message": "განახლებების შემოწმება ვერ მოხერხდა",
"desktop.updater.none.title": "განახლება არ არის ხელმისაწვდომი",
"desktop.updater.none.message": "თქვენ უკვე იყენებთ OpenCode-ის უახლეს ვერსიას",
"desktop.updater.downloadFailed.title": "განახლება ვერ მოხერხდა",
"desktop.updater.downloadFailed.message": "განახლების ჩამოტვირთვა ვერ მოხერხდა",
"desktop.updater.downloaded.title": "განახლება ჩამოიტვირთა",
"desktop.updater.downloaded.prompt":
"OpenCode-ის {{version}} ვერსია ჩამოტვირთულია, გსურთ მისი ინსტალაცია და ხელახლა გაშვება?",
"desktop.updater.installFailed.title": "განახლება ვერ მოხერხდა",
"desktop.updater.installFailed.message": "განახლების დაყენება ვერ მოხერხდა",
"desktop.error.dev.rootNotFound":
"ძირის ელემენტი ვერ მოიძებნა. დაგავიწყდათ მისი დამატება თქვენს index.html-ში? ან იქნებ id ატრიბუტი არასწორად არის დაწერილი?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "ពិនិត្យមើលបច្ចុប្បន្នភាព...",
"desktop.menu.reloadWebview": "ផ្ទុក Webview ឡើងវិញ",
"desktop.menu.restart": "ចាប់ផ្ដើមឡើងវិញ",
"desktop.dialog.chooseFolder": "ជ្រើសរើសថតឯកសារ",
"desktop.dialog.chooseFile": "ជ្រើសរើសឯកសារ",
"desktop.dialog.saveFile": "រក្សាទុកឯកសារ",
"desktop.updater.checkFailed.title": "ធីកអាប់ដេតបានបរាជ័យ",
"desktop.updater.checkFailed.message": "បានបរាជ័យក្នុងការពិនិត្យរកមើលបច្ចុប្បន្នភាព",
"desktop.updater.none.title": "មិនមានការអាប់ដេតទេ។",
"desktop.updater.none.message": "អ្នកកំពុងប្រើកំណែចុងក្រោយនៃ OpenCode រួចហើយ",
"desktop.updater.downloadFailed.title": "បរាជ័យក្នុងការអាប់ដេត",
"desktop.updater.downloadFailed.message": "បានបរាជ័យក្នុងការទាញយកបច្ចុប្បន្នភាព",
"desktop.updater.downloaded.title": "បានទាញយកបច្ចុប្បន្នភាព",
"desktop.updater.downloaded.prompt":
"កំណែ {{version}} នៃ OpenCode ត្រូវបានទាញយក តើអ្នកចង់ដំឡើងវា ហើយចាប់ផ្ដើមឡើងវិញទេ?",
"desktop.updater.installFailed.title": "ការអាប់ដេតបានបរាជ័យ",
"desktop.updater.installFailed.message": "បានបរាជ័យក្នុងការដំឡើងបច្ចុប្បន្នភាព",
"desktop.error.dev.rootNotFound":
"រកមិនឃើញធាតុឫសទេ។ តើអ្នកភ្លេចបន្ថែមវាទៅ index.html របស់អ្នកទេ? ឬប្រហែលជាគុណលក្ខណៈលេខសម្គាល់ត្រូវបានសរសេរខុស?",
}

View file

@ -1,23 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "업데이트 확인...",
"desktop.menu.reloadWebview": "WebView 새로 고침",
"desktop.menu.restart": "다시 시작",
"desktop.dialog.chooseFolder": "폴더 선택",
"desktop.dialog.chooseFile": "파일 선택",
"desktop.dialog.saveFile": "파일 저장",
"desktop.updater.checkFailed.title": "업데이트 확인 실패",
"desktop.updater.checkFailed.message": "업데이트를 확인하지 못했습니다",
"desktop.updater.none.title": "사용 가능한 업데이트 없음",
"desktop.updater.none.message": "이미 최신 버전의 OpenCode를 사용하고 있습니다",
"desktop.updater.downloadFailed.title": "업데이트 실패",
"desktop.updater.downloadFailed.message": "업데이트를 다운로드하지 못했습니다",
"desktop.updater.downloaded.title": "업데이트 다운로드 완료",
"desktop.updater.downloaded.prompt": "OpenCode {{version}} 버전을 다운로드했습니다. 설치하고 다시 실행할까요?",
"desktop.updater.installFailed.title": "업데이트 실패",
"desktop.updater.installFailed.message": "업데이트를 설치하지 못했습니다",
"desktop.error.dev.rootNotFound":
"루트 요소를 찾을 수 없습니다. index.html에 추가하는 것을 잊으셨나요? 또는 id 속성의 철자가 틀렸을 수 있습니다.",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "ກວດເບິ່ງການອັບເດດ...",
"desktop.menu.reloadWebview": "ໂຫຼດ Webview ຄືນໃໝ່",
"desktop.menu.restart": "ຣີສະຕາດ",
"desktop.dialog.chooseFolder": "ເລືອກໂຟນເດີ",
"desktop.dialog.chooseFile": "ເລືອກໄຟລ໌",
"desktop.dialog.saveFile": "ບັນທຶກໄຟລ໌",
"desktop.updater.checkFailed.title": "ກວດສອບການອັບເດດບໍ່ສຳເລັດ",
"desktop.updater.checkFailed.message": "ລົ້ມເຫລວໃນການກວດສອບການອັບເດດ",
"desktop.updater.none.title": "ບໍ່ມີການອັບເດດ",
"desktop.updater.none.message": "ທ່ານກຳລັງໃຊ້ OpenCode ເວີຊັນຫຼ້າສຸດຢູ່ແລ້ວ",
"desktop.updater.downloadFailed.title": "ການອັບເດດລົ້ມເຫລວ",
"desktop.updater.downloadFailed.message": "ລົ້ມເຫລວໃນການດາວໂຫຼດອັບເດດ",
"desktop.updater.downloaded.title": "ດາວໂຫຼດອັບເດດແລ້ວ",
"desktop.updater.downloaded.prompt":
"ເວີຊັນ {{version}} ຂອງ OpenCode ໄດ້ຖືກດາວໂຫຼດແລ້ວ, ທ່ານຕ້ອງການຕິດຕັ້ງມັນ ແລະເປີດຄືນໃໝ່ບໍ?",
"desktop.updater.installFailed.title": "ການອັບເດດລົ້ມເຫລວ",
"desktop.updater.installFailed.message": "ລົ້ມເຫລວໃນການຕິດຕັ້ງອັບເດດ",
"desktop.error.dev.rootNotFound":
"ບໍ່ພົບອົງປະກອບຮາກ. ທ່ານລືມເພີ່ມມັນໃສ່ index.html ຂອງທ່ານບໍ? ຫຼືບາງທີຄຸນສົມບັດ id ມີການສະກົດຜິດ?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Tikrinti, ar yra naujinimų...",
"desktop.menu.reloadWebview": "Iš naujo įkelti Webview",
"desktop.menu.restart": "Paleisti iš naujo",
"desktop.dialog.chooseFolder": "Pasirinkite aplanką",
"desktop.dialog.chooseFile": "Pasirinkite failą",
"desktop.dialog.saveFile": "Išsaugoti failą",
"desktop.updater.checkFailed.title": "Naujinių patikrinti nepavyko",
"desktop.updater.checkFailed.message": "Nepavyko patikrinti, ar nėra naujinimų",
"desktop.updater.none.title": "Naujinių nėra",
"desktop.updater.none.message": "Jau naudojate naujausią OpenCode versiją",
"desktop.updater.downloadFailed.title": "Nepavyko atnaujinti",
"desktop.updater.downloadFailed.message": "Nepavyko atsisiųsti naujinimo",
"desktop.updater.downloaded.title": "Naujinimas parsiųstas",
"desktop.updater.downloaded.prompt":
"OpenCode versija {{version}} atsisiųsta, ar norėtumėte ją įdiegti ir paleisti iš naujo?",
"desktop.updater.installFailed.title": "Nepavyko atnaujinti",
"desktop.updater.installFailed.message": "Nepavyko įdiegti naujinimo",
"desktop.error.dev.rootNotFound":
"Šakninis elementas nerastas. Ar pamiršote jį įtraukti į index.html? O gal id atributas buvo neteisingai parašytas?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Pārbaudīt atjauninājumus...",
"desktop.menu.reloadWebview": "Pārlādēt tīmekļa skatu",
"desktop.menu.restart": "Restartēt",
"desktop.dialog.chooseFolder": "Izvēlieties mapi",
"desktop.dialog.chooseFile": "Izvēlieties failu",
"desktop.dialog.saveFile": "Saglabāt failu",
"desktop.updater.checkFailed.title": "Atjauninājumu pārbaude neizdevās",
"desktop.updater.checkFailed.message": "Neizdevās pārbaudīt atjauninājumus",
"desktop.updater.none.title": "Atjauninājumu nav",
"desktop.updater.none.message": "Jūs jau izmantojat jaunāko OpenCode versiju",
"desktop.updater.downloadFailed.title": "Atjaunināšana neizdevās",
"desktop.updater.downloadFailed.message": "Neizdevās lejupielādēt atjauninājumu",
"desktop.updater.downloaded.title": "Atjauninājums lejupielādēts",
"desktop.updater.downloaded.prompt":
"OpenCode versija {{version}} ir lejupielādēta. Vai vēlaties to instalēt un palaist no jauna?",
"desktop.updater.installFailed.title": "Atjaunināšana neizdevās",
"desktop.updater.installFailed.message": "Neizdevās instalēt atjauninājumu",
"desktop.error.dev.rootNotFound":
"Saknes elements nav atrasts. Vai aizmirsāt to pievienot index.html? Vai arī id atribūts ir kļūdaini uzrakstīts?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Проверете дали има ажурирања...",
"desktop.menu.reloadWebview": "Вчитај повторно Webview",
"desktop.menu.restart": "Рестартирајте",
"desktop.dialog.chooseFolder": "Изберете папка",
"desktop.dialog.chooseFile": "Изберете датотека",
"desktop.dialog.saveFile": "Зачувај датотека",
"desktop.updater.checkFailed.title": "Проверката за ажурирање не успеа",
"desktop.updater.checkFailed.message": "Не успеа да се провери дали има ажурирања",
"desktop.updater.none.title": "Нема достапно ажурирање",
"desktop.updater.none.message": "Веќе ја користите најновата верзија на OpenCode",
"desktop.updater.downloadFailed.title": "Ажурирањето не успеа",
"desktop.updater.downloadFailed.message": "Не успеа да се преземе ажурирањето",
"desktop.updater.downloaded.title": "Ажурирањето е преземено",
"desktop.updater.downloaded.prompt":
"Верзијата {{version}} од OpenCode е преземена, дали сакате да ја инсталирате и повторно да ја стартувате?",
"desktop.updater.installFailed.title": "Ажурирањето не успеа",
"desktop.updater.installFailed.message": "Не успеа да се инсталира ажурирањето",
"desktop.error.dev.rootNotFound":
"Не е пронајден корен елемент. Дали заборавивте да го додадете во вашата index.html? Или можеби атрибутот id е погрешно напишан?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Шинэчлэлтүүдийг шалгана уу...",
"desktop.menu.reloadWebview": "Дахин ачаалах Webview",
"desktop.menu.restart": "Дахин эхлүүлэх",
"desktop.dialog.chooseFolder": "Фолдер сонгоно уу",
"desktop.dialog.chooseFile": "Файл сонгоно уу",
"desktop.dialog.saveFile": "Файлыг хадгалах",
"desktop.updater.checkFailed.title": "Шинэчлэх шалгалт амжилтгүй боллоо",
"desktop.updater.checkFailed.message": "Шинэчлэлтүүдийг шалгаж чадсангүй",
"desktop.updater.none.title": "Шинэчлэлт байхгүй",
"desktop.updater.none.message": "Та OpenCode-н хамгийн сүүлийн хувилбарыг аль хэдийн ашиглаж байна",
"desktop.updater.downloadFailed.title": "Шинэчилж чадсангүй",
"desktop.updater.downloadFailed.message": "Шинэчлэлтийг татаж авч чадсангүй",
"desktop.updater.downloaded.title": "Шинэчлэлтийг татаж авсан",
"desktop.updater.downloaded.prompt":
"OpenCode-ийн {{version}} хувилбарыг татаж авсан тул та үүнийг суулгаад дахин эхлүүлэхийг хүсэж байна уу?",
"desktop.updater.installFailed.title": "Шинэчилж чадсангүй",
"desktop.updater.installFailed.message": "Шинэчлэлтийг суулгаж чадсангүй",
"desktop.error.dev.rootNotFound":
"Үндэс элемент олдсонгүй. Та үүнийг index.html дээрээ нэмэхээ мартсан уу? Эсвэл id атрибутыг буруу бичсэн байж магадгүй юм уу?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Semak Kemas Kini...",
"desktop.menu.reloadWebview": "Muat Semula Paparan Web",
"desktop.menu.restart": "Mulakan Semula",
"desktop.dialog.chooseFolder": "Pilih folder",
"desktop.dialog.chooseFile": "Pilih fail",
"desktop.dialog.saveFile": "Simpan fail",
"desktop.updater.checkFailed.title": "Semakan Kemas Kini Gagal",
"desktop.updater.checkFailed.message": "Gagal menyemak kemas kini",
"desktop.updater.none.title": "Tiada Kemas Kini Tersedia",
"desktop.updater.none.message": "Anda sudah menggunakan versi terkini OpenCode",
"desktop.updater.downloadFailed.title": "Kemas Kini Gagal",
"desktop.updater.downloadFailed.message": "Gagal memuat turun kemas kini",
"desktop.updater.downloaded.title": "Kemas Kini Dimuat Turun",
"desktop.updater.downloaded.prompt":
"Versi {{version}} OpenCode telah dimuat turun, adakah anda ingin memasangnya dan melancarkan semula?",
"desktop.updater.installFailed.title": "Kemas Kini Gagal",
"desktop.updater.installFailed.message": "Gagal memasang kemas kini",
"desktop.error.dev.rootNotFound":
"Elemen root tidak ditemui. Adakah anda terlupa menambahnya ke index.html anda? Atau mungkin atribut id telah tersalah eja?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "အပ်ဒိတ်များအတွက် စစ်ဆေးပါ...",
"desktop.menu.reloadWebview": "Webview ကို ပြန်လည်စတင်ပါ။",
"desktop.menu.restart": "ပြန်လည်စတင်ပါ။",
"desktop.dialog.chooseFolder": "ဖိုင်တွဲတစ်ခုကို ရွေးပါ။",
"desktop.dialog.chooseFile": "ဖိုင်တစ်ခုကို ရွေးပါ။",
"desktop.dialog.saveFile": "ဖိုင်ကို သိမ်းဆည်းပါ။",
"desktop.updater.checkFailed.title": "အပ်ဒိတ်စစ်ဆေးမှု မအောင်မြင်ပါ။",
"desktop.updater.checkFailed.message": "အပ်ဒိတ်များကို စစ်ဆေးရန် မအောင်မြင်ပါ။",
"desktop.updater.none.title": "အပ်ဒိတ် မရနိုင်ပါ။",
"desktop.updater.none.message": "သင်သည် OpenCode ၏နောက်ဆုံးထွက်ဗားရှင်းကို အသုံးပြုနေပြီဖြစ်သည်။",
"desktop.updater.downloadFailed.title": "အပ်ဒိတ် မအောင်မြင်ပါ။",
"desktop.updater.downloadFailed.message": "အပ်ဒိတ်ကို ဒေါင်းလုဒ်လုပ်ရန် မအောင်မြင်ပါ။",
"desktop.updater.downloaded.title": "အပ်ဒိတ်ကို ဒေါင်းလုဒ်လုပ်ထားသည်။",
"desktop.updater.downloaded.prompt":
"OpenCode ၏ {{version}} ဗားရှင်းကို ဒေါင်းလုဒ်လုပ်ပြီးပြီ၊ ၎င်းကို ထည့်သွင်းပြီး ပြန်လည်စတင်လိုပါသလား။",
"desktop.updater.installFailed.title": "အပ်ဒိတ် မအောင်မြင်ပါ။",
"desktop.updater.installFailed.message": "အပ်ဒိတ်ကို ထည့်သွင်းရန် မအောင်မြင်ပါ။",
"desktop.error.dev.rootNotFound":
"အမြစ်ဒြပ်စင်ကို ရှာမတွေ့ပါ။ ၎င်းကို သင်၏ index.html တွင် ထည့်ရန် မေ့သွားပါသလား။ ဒါမှမဟုတ် id attribute က စာလုံးပေါင်းမှားနေသလား။",
}

View file

@ -1,22 +0,0 @@
export const dict: Record<string, string> = {
"desktop.menu.checkForUpdates": "अपडेटहरूको लागि जाँच गर्नुहोस्...",
"desktop.menu.reloadWebview": "Webview पुन: लोड गर्नुहोस्",
"desktop.menu.restart": "पुन: सुरु गर्नुहोस्",
"desktop.dialog.chooseFolder": "एउटा फोल्डर छान्नुहोस्",
"desktop.dialog.chooseFile": "एउटा फाइल छान्नुहोस्",
"desktop.dialog.saveFile": "फाइल बचत गर्नुहोस्",
"desktop.updater.checkFailed.title": "अपडेट जाँच असफल भयो",
"desktop.updater.checkFailed.message": "अद्यावधिकहरूको लागि जाँच गर्न असफल भयो",
"desktop.updater.none.title": "कुनै अद्यावधिक उपलब्ध छैन",
"desktop.updater.none.message": "तपाईंले पहिले नै OpenCode को नवीनतम संस्करण प्रयोग गरिरहनुभएको छ",
"desktop.updater.downloadFailed.title": "अपडेट गर्न सकिएन",
"desktop.updater.downloadFailed.message": "अपडेट डाउनलोड गर्न असफल भयो",
"desktop.updater.downloaded.title": "अपडेट डाउनलोड गरियो",
"desktop.updater.downloaded.prompt":
"OpenCode को संस्करण {{version}} डाउनलोड गरिएको छ, के तपाइँ यसलाई स्थापना गरेर पुन: लन्च गर्न चाहनुहुन्छ?",
"desktop.updater.installFailed.title": "अपडेट गर्न सकिएन",
"desktop.updater.installFailed.message": "अद्यावधिक स्थापना गर्न असफल भयो",
"desktop.error.dev.rootNotFound":
"मूल तत्व फेला परेन। के तपाईंले यसलाई आफ्नो index.html मा थप्न बिर्सनुभयो? वा हुनसक्छ आईडी विशेषता गलत हिज्जे भयो?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Controleren op updates...",
"desktop.menu.reloadWebview": "Webview opnieuw laden",
"desktop.menu.restart": "Opnieuw opstarten",
"desktop.dialog.chooseFolder": "Kies een map",
"desktop.dialog.chooseFile": "Kies een bestand",
"desktop.dialog.saveFile": "Bestand opslaan",
"desktop.updater.checkFailed.title": "Updatecontrole mislukt",
"desktop.updater.checkFailed.message": "Controleren op updates is mislukt",
"desktop.updater.none.title": "Geen update beschikbaar",
"desktop.updater.none.message": "Je gebruikt al de nieuwste versie van OpenCode",
"desktop.updater.downloadFailed.title": "Update mislukt",
"desktop.updater.downloadFailed.message": "Downloaden van update is mislukt",
"desktop.updater.downloaded.title": "Update gedownload",
"desktop.updater.downloaded.prompt":
"Versie {{version}} van OpenCode is gedownload. Wil je deze installeren en OpenCode opnieuw starten?",
"desktop.updater.installFailed.title": "Update mislukt",
"desktop.updater.installFailed.message": "Installeren van update is mislukt",
"desktop.error.dev.rootNotFound":
"Root-element niet gevonden. Ben je vergeten het toe te voegen aan je index.html? Of is het id-attribuut misschien verkeerd gespeld?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Se etter oppdateringer...",
"desktop.menu.reloadWebview": "Last inn Webview på nytt",
"desktop.menu.restart": "Start på nytt",
"desktop.dialog.chooseFolder": "Velg en mappe",
"desktop.dialog.chooseFile": "Velg en fil",
"desktop.dialog.saveFile": "Lagre fil",
"desktop.updater.checkFailed.title": "Oppdateringssjekk mislyktes",
"desktop.updater.checkFailed.message": "Kunne ikke se etter oppdateringer",
"desktop.updater.none.title": "Ingen oppdatering tilgjengelig",
"desktop.updater.none.message": "Du bruker allerede den nyeste versjonen av OpenCode",
"desktop.updater.downloadFailed.title": "Oppdatering mislyktes",
"desktop.updater.downloadFailed.message": "Kunne ikke laste ned oppdateringen",
"desktop.updater.downloaded.title": "Oppdatering lastet ned",
"desktop.updater.downloaded.prompt":
"Versjon {{version}} av OpenCode er lastet ned. Vil du installere den og starte på nytt?",
"desktop.updater.installFailed.title": "Oppdatering mislyktes",
"desktop.updater.installFailed.message": "Kunne ikke installere oppdateringen",
"desktop.error.dev.rootNotFound":
"Rotelement ikke funnet. Glemte du å legge det til i index.html? Eller kanskje id-attributtet er feilstavet?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "اپ ڈیٹس لئی چیک کرو...",
"desktop.menu.reloadWebview": "ویب ویو دوبارہ لوڈ کرو",
"desktop.menu.restart": "دوبارہ شروع کرو",
"desktop.dialog.chooseFolder": "اک فولڈر چنو",
"desktop.dialog.chooseFile": "اک فائل چنو",
"desktop.dialog.saveFile": "فائل محفوظ کرو",
"desktop.updater.checkFailed.title": "اپ ڈیٹ دی پڑتال ناکام ہو گئی",
"desktop.updater.checkFailed.message": "اپ ڈیٹاں دی پڑتال نئیں ہو سکی",
"desktop.updater.none.title": "کوئی اپ ڈیٹ دستیاب نئیں",
"desktop.updater.none.message": "تسی پہلے ای OpenCode دا تازہ ترین ورژن استعمال کر رئے او",
"desktop.updater.downloadFailed.title": "اپ ڈیٹ ناکام ہو گئی",
"desktop.updater.downloadFailed.message": "اپ ڈیٹ ڈاؤن لوڈ نئیں ہو سکی",
"desktop.updater.downloaded.title": "اپ ڈیٹ ڈاؤن لوڈ ہو گئی",
"desktop.updater.downloaded.prompt":
"OpenCode دا ورژن {{version}} ڈاؤن لوڈ کر دتا گیا اے، کی تسی اینوں انسٹال کرنا تے دوبارہ لانچ کرنا چاہندے او؟",
"desktop.updater.installFailed.title": "اپ ڈیٹ ناکام ہو گئی",
"desktop.updater.installFailed.message": "اپ ڈیٹ انسٹال نئیں ہو سکی",
"desktop.error.dev.rootNotFound":
"عنصر نئیں لبیا۔ کی تسی ایہنوں اپنے index.html چ شامل کرنا بھل گئے او؟ یا شاید id وصف غلط ہجے ہو گیا اے؟",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Sprawdź aktualizacje...",
"desktop.menu.reloadWebview": "Załaduj ponownie WebView",
"desktop.menu.restart": "Uruchom ponownie",
"desktop.dialog.chooseFolder": "Wybierz folder",
"desktop.dialog.chooseFile": "Wybierz plik",
"desktop.dialog.saveFile": "Zapisz plik",
"desktop.updater.checkFailed.title": "Nie udało się sprawdzić aktualizacji",
"desktop.updater.checkFailed.message": "Nie udało się sprawdzić aktualizacji",
"desktop.updater.none.title": "Brak dostępnych aktualizacji",
"desktop.updater.none.message": "Korzystasz już z najnowszej wersji OpenCode",
"desktop.updater.downloadFailed.title": "Aktualizacja nie powiodła się",
"desktop.updater.downloadFailed.message": "Nie udało się pobrać aktualizacji",
"desktop.updater.downloaded.title": "Aktualizacja pobrana",
"desktop.updater.downloaded.prompt":
"Pobrano wersję {{version}} OpenCode. Czy chcesz ją zainstalować i uruchomić ponownie?",
"desktop.updater.installFailed.title": "Aktualizacja nie powiodła się",
"desktop.updater.installFailed.message": "Nie udało się zainstalować aktualizacji",
"desktop.error.dev.rootNotFound":
"Nie znaleziono elementu głównego. Czy zapomniałeś dodać go do swojego index.html? A może atrybut id został błędnie wpisany?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Caută actualizări...",
"desktop.menu.reloadWebview": "Reîncarcă webview",
"desktop.menu.restart": "Repornește",
"desktop.dialog.chooseFolder": "Alege un folder",
"desktop.dialog.chooseFile": "Alege un fișier",
"desktop.dialog.saveFile": "Salvează fișierul",
"desktop.updater.checkFailed.title": "Verificarea actualizărilor a eșuat",
"desktop.updater.checkFailed.message": "Nu s-au putut verifica actualizările",
"desktop.updater.none.title": "Nicio actualizare disponibilă",
"desktop.updater.none.message": "Folosești deja cea mai recentă versiune OpenCode",
"desktop.updater.downloadFailed.title": "Actualizarea a eșuat",
"desktop.updater.downloadFailed.message": "Nu s-a putut descărca actualizarea",
"desktop.updater.downloaded.title": "Actualizare descărcată",
"desktop.updater.downloaded.prompt":
"Versiunea {{version}} OpenCode a fost descărcată. Vrei să o instalezi și să repornești?",
"desktop.updater.installFailed.title": "Actualizarea a eșuat",
"desktop.updater.installFailed.message": "Nu s-a putut instala actualizarea",
"desktop.error.dev.rootNotFound":
"Elementul root nu a fost găsit. L-ai adăugat în index.html? Sau poate atributul id este scris greșit?",
}

View file

@ -1,23 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Проверить обновления...",
"desktop.menu.reloadWebview": "Перезагрузить WebView",
"desktop.menu.restart": "Перезапустить",
"desktop.dialog.chooseFolder": "Выберите папку",
"desktop.dialog.chooseFile": "Выберите файл",
"desktop.dialog.saveFile": "Сохранить файл",
"desktop.updater.checkFailed.title": "Не удалось проверить обновления",
"desktop.updater.checkFailed.message": "Не удалось проверить обновления",
"desktop.updater.none.title": "Обновлений нет",
"desktop.updater.none.message": "Вы уже используете последнюю версию OpenCode",
"desktop.updater.downloadFailed.title": "Обновление не удалось",
"desktop.updater.downloadFailed.message": "Не удалось скачать обновление",
"desktop.updater.downloaded.title": "Обновление загружено",
"desktop.updater.downloaded.prompt": "Версия OpenCode {{version}} загружена. Хотите установить и перезапустить?",
"desktop.updater.installFailed.title": "Обновление не удалось",
"desktop.updater.installFailed.message": "Не удалось установить обновление",
"desktop.error.dev.rootNotFound":
"Корневой элемент не найден. Вы забыли добавить его в index.html? Или, может быть, атрибут id был написан неправильно?",
}

View file

@ -1,22 +0,0 @@
export const dict: Record<string, string> = {
"desktop.menu.checkForUpdates": "යාවත්කාලීන සඳහා පරීක්ෂා කරන්න...",
"desktop.menu.reloadWebview": "Webview නැවත පූරණය කරන්න",
"desktop.menu.restart": "යළි අරඹන්න",
"desktop.dialog.chooseFolder": "ෆෝල්ඩරයක් තෝරන්න",
"desktop.dialog.chooseFile": "ගොනුවක් තෝරන්න",
"desktop.dialog.saveFile": "ගොනුව සුරකින්න",
"desktop.updater.checkFailed.title": "යාවත්කාලීන පරීක්ෂාව අසාර්ථක විය",
"desktop.updater.checkFailed.message": "යාවත්කාලීන සඳහා පරීක්ෂා කිරීමට අසමත් විය",
"desktop.updater.none.title": "යාවත්කාලීනයක් නොමැත",
"desktop.updater.none.message": "ඔබ දැනටමත් OpenCode හි නවතම අනුවාදය භාවිතා කරයි",
"desktop.updater.downloadFailed.title": "යාවත්කාලීන කිරීම අසාර්ථක විය",
"desktop.updater.downloadFailed.message": "යාවත්කාලීන බාගැනීම අසාර්ථක විය",
"desktop.updater.downloaded.title": "යාවත්කාලීනය බාගත කර ඇත",
"desktop.updater.downloaded.prompt":
"OpenCode හි {{version}} අනුවාදය බාගෙන ඇත, ඔබ එය ස්ථාපනය කර නැවත දියත් කිරීමට කැමතිද?",
"desktop.updater.installFailed.title": "යාවත්කාලීන කිරීම අසාර්ථක විය",
"desktop.updater.installFailed.message": "යාවත්කාලීන ස්ථාපනය කිරීමට අසමත් විය",
"desktop.error.dev.rootNotFound":
"මූල මූලද්රව්යය හමු නොවීය. ඔබට එය ඔබගේ index.html වෙත එක් කිරීමට අමතකද? එසේත් නැතිනම් හැඳුනුම්පත වැරදි ලෙස සටහන් වී තිබේද?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Skontrolovať aktualizácie...",
"desktop.menu.reloadWebview": "Obnoviť webové zobrazenie",
"desktop.menu.restart": "Reštartovať",
"desktop.dialog.chooseFolder": "Vybrať priečinok",
"desktop.dialog.chooseFile": "Vybrať súbor",
"desktop.dialog.saveFile": "Uložiť súbor",
"desktop.updater.checkFailed.title": "Kontrola aktualizácií zlyhala",
"desktop.updater.checkFailed.message": "Nepodarilo sa skontrolovať aktualizácie",
"desktop.updater.none.title": "Žiadna aktualizácia nie je k dispozícii",
"desktop.updater.none.message": "Používate najnovšiu verziu OpenCode",
"desktop.updater.downloadFailed.title": "Aktualizácia zlyhala",
"desktop.updater.downloadFailed.message": "Nepodarilo sa stiahnuť aktualizáciu",
"desktop.updater.downloaded.title": "Aktualizácia stiahnutá",
"desktop.updater.downloaded.prompt":
"Verzia {{version}} OpenCode bola stiahnutá. Chcete ju nainštalovať a reštartovať?",
"desktop.updater.installFailed.title": "Aktualizácia zlyhala",
"desktop.updater.installFailed.message": "Nepodarilo sa nainštalovať aktualizáciu",
"desktop.error.dev.rootNotFound":
"Koreňový prvok sa nenašiel. Pridali ste ho do index.html? Alebo je atribút id napísaný nesprávne?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Preverite posodobitve ...",
"desktop.menu.reloadWebview": "Ponovno naloži Webview",
"desktop.menu.restart": "Znova zaženite",
"desktop.dialog.chooseFolder": "Izberite mapo",
"desktop.dialog.chooseFile": "Izberite datoteko",
"desktop.dialog.saveFile": "Shrani datoteko",
"desktop.updater.checkFailed.title": "Preverjanje posodobitve ni uspelo",
"desktop.updater.checkFailed.message": "Preverjanje posodobitev ni uspelo",
"desktop.updater.none.title": "Posodobitev ni na voljo",
"desktop.updater.none.message": "Že uporabljate najnovejšo različico OpenCode",
"desktop.updater.downloadFailed.title": "Posodobitev ni uspela",
"desktop.updater.downloadFailed.message": "Prenos posodobitve ni uspel",
"desktop.updater.downloaded.title": "Posodobitev prenesena",
"desktop.updater.downloaded.prompt":
"Različica {{version}} OpenCode je bila prenesena, jo želite namestiti in znova zagnati?",
"desktop.updater.installFailed.title": "Posodobitev ni uspela",
"desktop.updater.installFailed.message": "Namestitev posodobitve ni uspela",
"desktop.error.dev.rootNotFound":
"Korenski element ni bil najden. Ste ga pozabili dodati v svoj index.html? Ali pa je morda atribut id narobe črkovan?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Kontrollo për përditësime...",
"desktop.menu.reloadWebview": "Rifresko pamjen e internetit",
"desktop.menu.restart": "Rinis",
"desktop.dialog.chooseFolder": "Zgjidhni një dosje",
"desktop.dialog.chooseFile": "Zgjidhni një skedar",
"desktop.dialog.saveFile": "Ruaj skedarin",
"desktop.updater.checkFailed.title": "Kontrolli i përditësimit dështoi",
"desktop.updater.checkFailed.message": "Kontrolli për përditësime dështoi",
"desktop.updater.none.title": "Nuk ka përditësim të disponueshëm",
"desktop.updater.none.message": "Ju tashmë po përdorni versionin më të fundit të OpenCode",
"desktop.updater.downloadFailed.title": "Përditësimi dështoi",
"desktop.updater.downloadFailed.message": "Shkarkimi i përditësimit dështoi",
"desktop.updater.downloaded.title": "Përditësimi u shkarkua",
"desktop.updater.downloaded.prompt":
"Versioni {{version}} i OpenCode është shkarkuar, dëshironi ta instaloni dhe rinisni?",
"desktop.updater.installFailed.title": "Përditësimi dështoi",
"desktop.updater.installFailed.message": "Instalimi i përditësimit dështoi",
"desktop.error.dev.rootNotFound":
"Elementi rrënjë nuk u gjet. A keni harruar ta shtoni atë në index.html tuaj? Apo ndoshta atributi id është shkruar gabim?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Проверите да ли постоје ажурирања...",
"desktop.menu.reloadWebview": "Поново учитај Webview",
"desktop.menu.restart": "Поново покрените",
"desktop.dialog.chooseFolder": "Изаберите фасциклу",
"desktop.dialog.chooseFile": "Изаберите датотеку",
"desktop.dialog.saveFile": "Сачувај датотеку",
"desktop.updater.checkFailed.title": "Провера ажурирања није успела",
"desktop.updater.checkFailed.message": "Провера ажурирања није успела",
"desktop.updater.none.title": "Ажурирање није доступно",
"desktop.updater.none.message": "Већ користите најновију верзију OpenCode",
"desktop.updater.downloadFailed.title": "Ажурирање није успело",
"desktop.updater.downloadFailed.message": "Преузимање ажурирања није успело",
"desktop.updater.downloaded.title": "Преузето ажурирање",
"desktop.updater.downloaded.prompt":
"Верзија {{version}} од OpenCode је преузета, да ли желите да је инсталирате и поново покренете?",
"desktop.updater.installFailed.title": "Ажурирање није успело",
"desktop.updater.installFailed.message": "Инсталација ажурирања није успела",
"desktop.error.dev.rootNotFound":
"Основни елемент није пронађен. Да ли сте заборавили да га додате у свој index.html? Или је можда атрибут ид погрешно написан?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Sök efter uppdateringar...",
"desktop.menu.reloadWebview": "Ladda om webbvyn",
"desktop.menu.restart": "Starta om",
"desktop.dialog.chooseFolder": "Välj en mapp",
"desktop.dialog.chooseFile": "Välj en fil",
"desktop.dialog.saveFile": "Spara filen",
"desktop.updater.checkFailed.title": "Uppdateringskontrollen misslyckades",
"desktop.updater.checkFailed.message": "Det gick inte att söka efter uppdateringar",
"desktop.updater.none.title": "Ingen uppdatering tillgänglig",
"desktop.updater.none.message": "Du använder redan den senaste versionen av OpenCode",
"desktop.updater.downloadFailed.title": "Uppdateringen misslyckades",
"desktop.updater.downloadFailed.message": "Det gick inte att ladda ned uppdateringen",
"desktop.updater.downloaded.title": "Uppdatering nedladdad",
"desktop.updater.downloaded.prompt":
"Version {{version}} av OpenCode har laddats ned. Vill du installera den och starta om programmet?",
"desktop.updater.installFailed.title": "Uppdateringen misslyckades",
"desktop.updater.installFailed.message": "Det gick inte att installera uppdateringen",
"desktop.error.dev.rootNotFound":
"Rotelementet hittades inte. Har du glömt att lägga till det i din index.html? Eller kanske id-attributet är felstavat?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Навсозиро санҷед...",
"desktop.menu.reloadWebview": "Аз нав бор кунед Webview",
"desktop.menu.restart": "Оғози дубора",
"desktop.dialog.chooseFolder": "Папкаро интихоб кунед",
"desktop.dialog.chooseFile": "Файлро интихоб кунед",
"desktop.dialog.saveFile": "Файлро захира кунед",
"desktop.updater.checkFailed.title": "Санҷиши навсозӣ иҷро нашуд",
"desktop.updater.checkFailed.message": "Санҷиши навсозиҳо муяссар нашуд",
"desktop.updater.none.title": "Навсозии дастрас нест",
"desktop.updater.none.message": "Шумо аллакай версияи охирини OpenCode-ро истифода мебаред",
"desktop.updater.downloadFailed.title": "Навсозӣ ноком шуд",
"desktop.updater.downloadFailed.message": "Боргирии навсозӣ муваффақ нашуд",
"desktop.updater.downloaded.title": "Навсозии зеркашӣ",
"desktop.updater.downloaded.prompt":
"Версияи {{version}} аз OpenCode зеркашӣ карда шуд, оё шумо мехоҳед онро насб кунед ва аз нав оғоз кунед?",
"desktop.updater.installFailed.title": "Навсозӣ ноком шуд",
"desktop.updater.installFailed.message": "Навсозӣ насб карда нашуд",
"desktop.error.dev.rootNotFound":
"Элементи решавӣ ёфт нашуд. Оё шумо онро ба index.html илова карданро фаромӯш кардаед? Ё шояд атрибути id хато навишта шудааст?",
}

View file

@ -1,23 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "ตรวจหาการอัปเดต...",
"desktop.menu.reloadWebview": "โหลด Webview ใหม่",
"desktop.menu.restart": "เริ่มการทำงานใหม่",
"desktop.dialog.chooseFolder": "เลือกโฟลเดอร์",
"desktop.dialog.chooseFile": "เลือกไฟล์",
"desktop.dialog.saveFile": "บันทึกไฟล์",
"desktop.updater.checkFailed.title": "การตรวจหาการอัปเดตล้มเหลว",
"desktop.updater.checkFailed.message": "ไม่สามารถตรวจหาการอัปเดตได้",
"desktop.updater.none.title": "ไม่มีการอัปเดต",
"desktop.updater.none.message": "คุณกำลังใช้ OpenCode เวอร์ชันล่าสุดอยู่แล้ว",
"desktop.updater.downloadFailed.title": "การอัปเดตล้มเหลว",
"desktop.updater.downloadFailed.message": "ไม่สามารถดาวน์โหลดการอัปเดตได้",
"desktop.updater.downloaded.title": "ดาวน์โหลดการอัปเดตแล้ว",
"desktop.updater.downloaded.prompt":
"ดาวน์โหลด OpenCode เวอร์ชัน {{version}} แล้ว คุณต้องการติดตั้งและเปิดแอปอีกครั้งหรือไม่",
"desktop.updater.installFailed.title": "การอัปเดตล้มเหลว",
"desktop.updater.installFailed.message": "ไม่สามารถติดตั้งการอัปเดตได้",
"desktop.error.dev.rootNotFound": "ไม่พบองค์ประกอบรูท คุณลืมเพิ่มใน index.html หรือบางทีแอตทริบิวต์ id อาจสะกดผิด?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Täzelenmeleri barlaň ...",
"desktop.menu.reloadWebview": "Web sahypasyny täzeden ýükläň",
"desktop.menu.restart": "Gaýtadan açyň",
"desktop.dialog.chooseFolder": "Papka saýlaň",
"desktop.dialog.chooseFile": "Faýl saýlaň",
"desktop.dialog.saveFile": "Faýly ýazdyryň",
"desktop.updater.checkFailed.title": "Täzelenme şowsuz",
"desktop.updater.checkFailed.message": "Täzelenmeleri barlap bilmedi",
"desktop.updater.none.title": "Täzelenme ýok",
"desktop.updater.none.message": "OpenCode-iň iň soňky wersiýasyny eýýäm ulanýarsyňyz",
"desktop.updater.downloadFailed.title": "Täzelenme şowsuz",
"desktop.updater.downloadFailed.message": "Täzelenmäni göçürip alyp bilmedi",
"desktop.updater.downloaded.title": "Täzelenme ýüklendi",
"desktop.updater.downloaded.prompt":
"OpenCode-iň {{version}} wersiýasy göçürildi, ony gurup täzeden işletmek isleýärsiňizmi?",
"desktop.updater.installFailed.title": "Täzelenme şowsuz",
"desktop.updater.installFailed.message": "Täzelenmäni gurup bilmedi",
"desktop.error.dev.rootNotFound":
"Kök elementi tapylmady index.html-e goşmagy ýatdan çykardyňyzmy? Ora-da id atributynyň ýalňyş ýazylan bolmagy mümkin?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Güncellemeleri kontrol et...",
"desktop.menu.reloadWebview": "Web görünümünü yeniden yükle",
"desktop.menu.restart": "Yeniden başlat",
"desktop.dialog.chooseFolder": "Bir klasör seçin",
"desktop.dialog.chooseFile": "Bir dosya seçin",
"desktop.dialog.saveFile": "Dosyayı kaydedin",
"desktop.updater.checkFailed.title": "Güncelleme kontrolü başarısız oldu",
"desktop.updater.checkFailed.message": "Güncellemeler kontrol edilemedi",
"desktop.updater.none.title": "Güncelleme yok",
"desktop.updater.none.message": "OpenCode'un en son sürümünü zaten kullanıyorsunuz",
"desktop.updater.downloadFailed.title": "Güncelleme başarısız oldu",
"desktop.updater.downloadFailed.message": "Güncelleme indirilemedi",
"desktop.updater.downloaded.title": "Güncelleme indirildi",
"desktop.updater.downloaded.prompt":
"OpenCode'un {{version}} sürümü indirildi. Şimdi yükleyip yeniden başlatmak ister misiniz?",
"desktop.updater.installFailed.title": "Güncelleme başarısız oldu",
"desktop.updater.installFailed.message": "Güncelleme yüklenemedi",
"desktop.error.dev.rootNotFound":
"Kök eleman bulunamadı. index.html dosyanıza eklemeyi unuttunuz mu? Ya da id özelliği yanlış mı yazıldı?",
}

View file

@ -1,24 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Перевірити оновлення...",
"desktop.menu.reloadWebview": "Перезавантажити Webview",
"desktop.menu.restart": "Перезапустити",
"desktop.dialog.chooseFolder": "Виберіть папку",
"desktop.dialog.chooseFile": "Виберіть файл",
"desktop.dialog.saveFile": "Зберегти файл",
"desktop.updater.checkFailed.title": "Не вдалося перевірити оновлення",
"desktop.updater.checkFailed.message": "Не вдалося перевірити наявність оновлень",
"desktop.updater.none.title": "Немає доступних оновлень",
"desktop.updater.none.message": "Ви вже використовуєте найновішу версію OpenCode",
"desktop.updater.downloadFailed.title": "Помилка оновлення",
"desktop.updater.downloadFailed.message": "Не вдалося завантажити оновлення",
"desktop.updater.downloaded.title": "Оновлення завантажено",
"desktop.updater.downloaded.prompt":
"Версію {{version}} OpenCode завантажено. Бажаєте встановити її та перезапустити?",
"desktop.updater.installFailed.title": "Помилка оновлення",
"desktop.updater.installFailed.message": "Не вдалося встановити оновлення",
"desktop.error.dev.rootNotFound":
"Кореневий елемент не знайдено. Ви забули додати його до index.html? Або, можливо, атрибут id було написано з помилкою?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "اپ ڈیٹس کے لیے چیک کریں...",
"desktop.menu.reloadWebview": "ویب ویو کو دوبارہ لوڈ کریں۔",
"desktop.menu.restart": "دوبارہ شروع کریں۔",
"desktop.dialog.chooseFolder": "ایک فولڈر منتخب کریں۔",
"desktop.dialog.chooseFile": "ایک فائل کا انتخاب کریں۔",
"desktop.dialog.saveFile": "فائل کو محفوظ کریں۔",
"desktop.updater.checkFailed.title": "اپ ڈیٹ کی جانچ ناکام ہو گئی",
"desktop.updater.checkFailed.message": "اپ ڈیٹس چیک کرنے میں ناکام",
"desktop.updater.none.title": "کوئی اپ ڈیٹ دستیاب نہیں",
"desktop.updater.none.message": "آپ پہلے ہی OpenCode کا تازہ ترین ورژن استعمال کر رہے ہیں۔",
"desktop.updater.downloadFailed.title": "اپ ڈیٹ ناکام ہو گیا۔",
"desktop.updater.downloadFailed.message": "اپ ڈیٹ ڈاؤن لوڈ کرنے میں ناکام",
"desktop.updater.downloaded.title": "اپ ڈیٹ ڈاؤن لوڈ ہو گیا۔",
"desktop.updater.downloaded.prompt":
"OpenCode کا ورژن {{version}} ڈاؤن لوڈ ہو چکا ہے، کیا آپ اسے انسٹال کر کے دوبارہ لانچ کرنا چاہیں گے؟",
"desktop.updater.installFailed.title": "اپ ڈیٹ ناکام ہو گیا۔",
"desktop.updater.installFailed.message": "اپ ڈیٹ انسٹال کرنے میں ناکام",
"desktop.error.dev.rootNotFound":
"روٹ عنصر نہیں ملا۔ کیا آپ اسے اپنے index.html میں شامل کرنا بھول گئے؟ یا ہو سکتا ہے کہ آئی ڈی وصف کی ہجے غلط ہو گئی ہو؟",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Yangilanishlarni tekshiring...",
"desktop.menu.reloadWebview": "Veb koʻrinishini qayta yuklash",
"desktop.menu.restart": "Qayta ishga tushirish",
"desktop.dialog.chooseFolder": "Jildni tanlang",
"desktop.dialog.chooseFile": "Faylni tanlang",
"desktop.dialog.saveFile": "Faylni saqlash",
"desktop.updater.checkFailed.title": "Yangilanish tekshiruvi amalga oshmadi",
"desktop.updater.checkFailed.message": "Yangilanishlarni tekshirib boʻlmadi",
"desktop.updater.none.title": "Yangilanish mavjud emas",
"desktop.updater.none.message": "Siz allaqachon OpenCode ning oxirgi versiyasidan foydalanyapsiz",
"desktop.updater.downloadFailed.title": "Yangilash amalga oshmadi",
"desktop.updater.downloadFailed.message": "Yangilanishni yuklab boʻlmadi",
"desktop.updater.downloaded.title": "Yangilanish yuklab olindi",
"desktop.updater.downloaded.prompt":
"OpenCodening {{version}} versiyasi yuklab olindi, uni oʻrnatib, qayta ishga tushirmoqchimisiz?",
"desktop.updater.installFailed.title": "Yangilash amalga oshmadi",
"desktop.updater.installFailed.message": "Yangilanishni oʻrnatib boʻlmadi",
"desktop.error.dev.rootNotFound":
"Ildiz element topilmadi. Uni index.html-ga qo'shishni unutdingizmi? Yoki id atributi noto'g'ri yozilgandir?",
}

View file

@ -1,22 +0,0 @@
export const dict = {
"desktop.menu.checkForUpdates": "Kiểm tra cập nhật...",
"desktop.menu.reloadWebview": "Tải lại Webview",
"desktop.menu.restart": "Khởi động lại",
"desktop.dialog.chooseFolder": "Chọn một thư mục",
"desktop.dialog.chooseFile": "Chọn một tệp",
"desktop.dialog.saveFile": "Lưu tệp",
"desktop.updater.checkFailed.title": "Kiểm tra cập nhật không thành công",
"desktop.updater.checkFailed.message": "Không thể kiểm tra cập nhật",
"desktop.updater.none.title": "Không có bản cập nhật nào",
"desktop.updater.none.message": "Bạn đang sử dụng phiên bản mới nhất của OpenCode",
"desktop.updater.downloadFailed.title": "Cập nhật không thành công",
"desktop.updater.downloadFailed.message": "Không tải được bản cập nhật xuống",
"desktop.updater.downloaded.title": "Đã tải xuống bản cập nhật",
"desktop.updater.downloaded.prompt":
"Phiên bản {{version}} của OpenCode đã được tải xuống, bạn có muốn cài đặt và khởi chạy lại không?",
"desktop.updater.installFailed.title": "Cập nhật không thành công",
"desktop.updater.installFailed.message": "Không cài đặt được bản cập nhật",
"desktop.error.dev.rootNotFound":
"Không tìm thấy phần tử gốc. Bạn đã quên thêm nó vào index.html của mình? Hoặc có thể thuộc tính id bị sai chính tả?",
}

Some files were not shown because too many files have changed in this diff Show more