summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/context
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-03-13 06:48:38 -0500
committerGitHub <[email protected]>2026-03-13 06:48:38 -0500
commit05cb3c87ca387be41aceb5ccad978c6848a56f70 (patch)
tree2b592d2aa90d0fdb7ea72aa392507e2277b92ba5 /packages/app/src/context
parent270cb0b8b4265ac0965ac8b94a58a3bca86fa558 (diff)
downloadopencode-05cb3c87ca387be41aceb5ccad978c6848a56f70.tar.gz
opencode-05cb3c87ca387be41aceb5ccad978c6848a56f70.zip
chore(app): i18n sync (#17283)
Diffstat (limited to 'packages/app/src/context')
-rw-r--r--packages/app/src/context/command.tsx61
-rw-r--r--packages/app/src/context/file.tsx6
-rw-r--r--packages/app/src/context/global-sdk.tsx6
-rw-r--r--packages/app/src/context/global-sync.tsx1
-rw-r--r--packages/app/src/context/global-sync/bootstrap.ts2
-rw-r--r--packages/app/src/context/global-sync/child-store.test.ts1
-rw-r--r--packages/app/src/context/global-sync/child-store.ts9
-rw-r--r--packages/app/src/context/terminal-title.ts51
-rw-r--r--packages/app/src/context/terminal.tsx11
9 files changed, 121 insertions, 27 deletions
diff --git a/packages/app/src/context/command.tsx b/packages/app/src/context/command.tsx
index 03bd6318d..2c6799d12 100644
--- a/packages/app/src/context/command.tsx
+++ b/packages/app/src/context/command.tsx
@@ -2,6 +2,7 @@ import { createEffect, createMemo, onCleanup, onMount, type Accessor } from "sol
import { createStore } from "solid-js/store"
import { createSimpleContext } from "@opencode-ai/ui/context"
import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { dict as en } from "@/i18n/en"
import { useLanguage } from "@/context/language"
import { useSettings } from "@/context/settings"
import { Persist, persisted } from "@/utils/persist"
@@ -13,6 +14,27 @@ const DEFAULT_PALETTE_KEYBIND = "mod+shift+p"
const SUGGESTED_PREFIX = "suggested."
const EDITABLE_KEYBIND_IDS = new Set(["terminal.toggle", "terminal.new", "file.attach"])
+type KeyLabel =
+ | "common.key.ctrl"
+ | "common.key.alt"
+ | "common.key.shift"
+ | "common.key.meta"
+ | "common.key.space"
+ | "common.key.backspace"
+ | "common.key.enter"
+ | "common.key.tab"
+ | "common.key.delete"
+ | "common.key.home"
+ | "common.key.end"
+ | "common.key.pageUp"
+ | "common.key.pageDown"
+ | "common.key.insert"
+ | "common.key.esc"
+
+function keyText(key: KeyLabel, t?: (key: KeyLabel) => string) {
+ return t ? t(key) : en[key]
+}
+
function actionId(id: string) {
if (!id.startsWith(SUGGESTED_PREFIX)) return id
return id.slice(SUGGESTED_PREFIX.length)
@@ -145,7 +167,7 @@ export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean
return false
}
-export function formatKeybind(config: string): string {
+export function formatKeybind(config: string, t?: (key: KeyLabel) => string): string {
if (!config || config === "none") return ""
const keybinds = parseKeybind(config)
@@ -154,10 +176,10 @@ export function formatKeybind(config: string): string {
const kb = keybinds[0]
const parts: string[] = []
- if (kb.ctrl) parts.push(IS_MAC ? "⌃" : "Ctrl")
- if (kb.alt) parts.push(IS_MAC ? "⌥" : "Alt")
- if (kb.shift) parts.push(IS_MAC ? "⇧" : "Shift")
- if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta")
+ if (kb.ctrl) parts.push(IS_MAC ? "⌃" : keyText("common.key.ctrl", t))
+ if (kb.alt) parts.push(IS_MAC ? "⌥" : keyText("common.key.alt", t))
+ if (kb.shift) parts.push(IS_MAC ? "⇧" : keyText("common.key.shift", t))
+ if (kb.meta) parts.push(IS_MAC ? "⌘" : keyText("common.key.meta", t))
if (kb.key) {
const keys: Record<string, string> = {
@@ -167,10 +189,29 @@ export function formatKeybind(config: string): string {
arrowright: "→",
comma: ",",
plus: "+",
- space: "Space",
+ }
+ const named: Record<string, KeyLabel> = {
+ backspace: "common.key.backspace",
+ delete: "common.key.delete",
+ end: "common.key.end",
+ enter: "common.key.enter",
+ esc: "common.key.esc",
+ escape: "common.key.esc",
+ home: "common.key.home",
+ insert: "common.key.insert",
+ pagedown: "common.key.pageDown",
+ pageup: "common.key.pageUp",
+ space: "common.key.space",
+ tab: "common.key.tab",
}
const key = kb.key.toLowerCase()
- const displayKey = keys[key] ?? (key.length === 1 ? key.toUpperCase() : key.charAt(0).toUpperCase() + key.slice(1))
+ const displayKey =
+ keys[key] ??
+ (named[key]
+ ? keyText(named[key], t)
+ : key.length === 1
+ ? key.toUpperCase()
+ : key.charAt(0).toUpperCase() + key.slice(1))
parts.push(displayKey)
}
@@ -364,17 +405,17 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
},
keybind(id: string) {
if (id === PALETTE_ID) {
- return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND)
+ return formatKeybind(settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND, language.t)
}
const base = actionId(id)
const option = options().find((x) => actionId(x.id) === base)
- if (option?.keybind) return formatKeybind(option.keybind)
+ if (option?.keybind) return formatKeybind(option.keybind, language.t)
const meta = catalog[base]
const config = bind(base, meta?.keybind)
if (!config) return ""
- return formatKeybind(config)
+ return formatKeybind(config, language.t)
},
show: showPalette,
keybinds(enabled: boolean) {
diff --git a/packages/app/src/context/file.tsx b/packages/app/src/context/file.tsx
index 99c6d2e42..f8fec7142 100644
--- a/packages/app/src/context/file.tsx
+++ b/packages/app/src/context/file.tsx
@@ -43,10 +43,10 @@ export {
touchFileContent,
}
-function errorMessage(error: unknown) {
+function errorMessage(error: unknown, fallback: string) {
if (error instanceof Error && error.message) return error.message
if (typeof error === "string" && error) return error
- return "Unknown error"
+ return fallback
}
export const { use: useFile, provider: FileProvider } = createSimpleContext({
@@ -184,7 +184,7 @@ export const { use: useFile, provider: FileProvider } = createSimpleContext({
})
.catch((e) => {
if (scope() !== directory) return
- setLoadError(file, errorMessage(e))
+ setLoadError(file, errorMessage(e, language.t("error.chain.unknown")))
})
.finally(() => {
inflight.delete(key)
diff --git a/packages/app/src/context/global-sdk.tsx b/packages/app/src/context/global-sdk.tsx
index c1a87b95b..60e9fd6d5 100644
--- a/packages/app/src/context/global-sdk.tsx
+++ b/packages/app/src/context/global-sdk.tsx
@@ -4,6 +4,7 @@ import { createGlobalEmitter } from "@solid-primitives/event-bus"
import { batch, onCleanup } from "solid-js"
import z from "zod"
import { createSdkForServer } from "@/utils/server"
+import { useLanguage } from "./language"
import { usePlatform } from "./platform"
import { useServer } from "./server"
@@ -14,6 +15,7 @@ const abortError = z.object({
export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleContext({
name: "GlobalSDK",
init: () => {
+ const language = useLanguage()
const server = useServer()
const platform = usePlatform()
const abort = new AbortController()
@@ -30,7 +32,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
})()
const currentServer = server.current
- if (!currentServer) throw new Error("No server available")
+ if (!currentServer) throw new Error(language.t("error.globalSDK.noServerAvailable"))
const eventSdk = createSdkForServer({
signal: abort.signal,
@@ -218,7 +220,7 @@ export const { use: useGlobalSDK, provider: GlobalSDKProvider } = createSimpleCo
event: emitter,
createClient(opts: Omit<Parameters<typeof createSdkForServer>[0], "server" | "fetch">) {
const s = server.current
- if (!s) throw new Error("Server not available")
+ if (!s) throw new Error(language.t("error.globalSDK.serverNotAvailable"))
return createSdkForServer({
server: s.http,
fetch: platform.fetch,
diff --git a/packages/app/src/context/global-sync.tsx b/packages/app/src/context/global-sync.tsx
index 1b6cdf530..c84098869 100644
--- a/packages/app/src/context/global-sync.tsx
+++ b/packages/app/src/context/global-sync.tsx
@@ -164,6 +164,7 @@ function createGlobalSync() {
sdkCache.delete(directory)
clearSessionPrefetchDirectory(directory)
},
+ translate: language.t,
})
const sdkFor = (directory: string) => {
diff --git a/packages/app/src/context/global-sync/bootstrap.ts b/packages/app/src/context/global-sync/bootstrap.ts
index 8b1a3c48c..13494b7ad 100644
--- a/packages/app/src/context/global-sync/bootstrap.ts
+++ b/packages/app/src/context/global-sync/bootstrap.ts
@@ -139,7 +139,7 @@ export async function bootstrapDirectory(input: {
const project = getFilename(input.directory)
showToast({
variant: "error",
- title: `Failed to reload ${project}`,
+ title: input.translate("toast.project.reloadFailed.title", { project }),
description: formatServerError(err, input.translate),
})
input.setStore("status", "partial")
diff --git a/packages/app/src/context/global-sync/child-store.test.ts b/packages/app/src/context/global-sync/child-store.test.ts
index cec76ff87..eee763f16 100644
--- a/packages/app/src/context/global-sync/child-store.test.ts
+++ b/packages/app/src/context/global-sync/child-store.test.ts
@@ -21,6 +21,7 @@ describe("createChildStoreManager", () => {
isLoadingSessions: () => false,
onBootstrap() {},
onDispose() {},
+ translate: (key) => key,
})
Array.from({ length: 30 }, (_, index) => `/pinned-${index}`).forEach((directory) => {
diff --git a/packages/app/src/context/global-sync/child-store.ts b/packages/app/src/context/global-sync/child-store.ts
index e2ada244f..d5904c609 100644
--- a/packages/app/src/context/global-sync/child-store.ts
+++ b/packages/app/src/context/global-sync/child-store.ts
@@ -21,6 +21,7 @@ export function createChildStoreManager(input: {
isLoadingSessions: (directory: string) => boolean
onBootstrap: (directory: string) => void
onDispose: (directory: string) => void
+ translate: (key: string, vars?: Record<string, string | number>) => string
}) {
const children: Record<string, [Store<State>, SetStoreFunction<State>]> = {}
const vcsCache = new Map<string, VcsCache>()
@@ -129,7 +130,7 @@ export function createChildStoreManager(input: {
createStore({ value: undefined as VcsInfo | undefined }),
),
)
- if (!vcs) throw new Error("Failed to create persisted cache")
+ if (!vcs) throw new Error(input.translate("error.childStore.persistedCacheCreateFailed"))
const vcsStore = vcs[0]
vcsCache.set(directory, { store: vcsStore, setStore: vcs[1], ready: vcs[3] })
@@ -139,7 +140,7 @@ export function createChildStoreManager(input: {
createStore({ value: undefined as ProjectMeta | undefined }),
),
)
- if (!meta) throw new Error("Failed to create persisted project metadata")
+ if (!meta) throw new Error(input.translate("error.childStore.persistedProjectMetadataCreateFailed"))
metaCache.set(directory, { store: meta[0], setStore: meta[1], ready: meta[3] })
const icon = runWithOwner(input.owner, () =>
@@ -148,7 +149,7 @@ export function createChildStoreManager(input: {
createStore({ value: undefined as string | undefined }),
),
)
- if (!icon) throw new Error("Failed to create persisted project icon")
+ if (!icon) throw new Error(input.translate("error.childStore.persistedProjectIconCreateFailed"))
iconCache.set(directory, { store: icon[0], setStore: icon[1], ready: icon[3] })
const init = () =>
@@ -211,7 +212,7 @@ export function createChildStoreManager(input: {
}
mark(directory)
const childStore = children[directory]
- if (!childStore) throw new Error("Failed to create store")
+ if (!childStore) throw new Error(input.translate("error.childStore.storeCreateFailed"))
return childStore
}
diff --git a/packages/app/src/context/terminal-title.ts b/packages/app/src/context/terminal-title.ts
new file mode 100644
index 000000000..3e8fa9af2
--- /dev/null
+++ b/packages/app/src/context/terminal-title.ts
@@ -0,0 +1,51 @@
+import { dict as ar } from "@/i18n/ar"
+import { dict as br } from "@/i18n/br"
+import { dict as bs } from "@/i18n/bs"
+import { dict as da } from "@/i18n/da"
+import { dict as de } from "@/i18n/de"
+import { dict as en } from "@/i18n/en"
+import { dict as es } from "@/i18n/es"
+import { dict as fr } from "@/i18n/fr"
+import { dict as ja } from "@/i18n/ja"
+import { dict as ko } from "@/i18n/ko"
+import { dict as no } from "@/i18n/no"
+import { dict as pl } from "@/i18n/pl"
+import { dict as ru } from "@/i18n/ru"
+import { dict as th } from "@/i18n/th"
+import { dict as tr } from "@/i18n/tr"
+import { dict as zh } from "@/i18n/zh"
+import { dict as zht } from "@/i18n/zht"
+
+const numbered = Array.from(
+ new Set([
+ en["terminal.title.numbered"],
+ ar["terminal.title.numbered"],
+ br["terminal.title.numbered"],
+ bs["terminal.title.numbered"],
+ da["terminal.title.numbered"],
+ de["terminal.title.numbered"],
+ es["terminal.title.numbered"],
+ fr["terminal.title.numbered"],
+ ja["terminal.title.numbered"],
+ ko["terminal.title.numbered"],
+ no["terminal.title.numbered"],
+ pl["terminal.title.numbered"],
+ ru["terminal.title.numbered"],
+ th["terminal.title.numbered"],
+ tr["terminal.title.numbered"],
+ zh["terminal.title.numbered"],
+ zht["terminal.title.numbered"],
+ ]),
+)
+
+export function defaultTitle(number: number) {
+ return en["terminal.title.numbered"].replace("{{number}}", String(number))
+}
+
+export function isDefaultTitle(title: string, number: number) {
+ return numbered.some((text) => title === text.replace("{{number}}", String(number)))
+}
+
+export function titleNumber(title: string, max: number) {
+ return Array.from({ length: max }, (_, idx) => idx + 1).find((number) => isDefaultTitle(title, number))
+}
diff --git a/packages/app/src/context/terminal.tsx b/packages/app/src/context/terminal.tsx
index a2807375f..e65c16788 100644
--- a/packages/app/src/context/terminal.tsx
+++ b/packages/app/src/context/terminal.tsx
@@ -4,6 +4,7 @@ import { batch, createEffect, createMemo, createRoot, on, onCleanup } from "soli
import { useParams } from "@solidjs/router"
import { useSDK } from "./sdk"
import type { Platform } from "./platform"
+import { defaultTitle, titleNumber } from "./terminal-title"
import { Persist, persisted, removePersisted } from "@/utils/persist"
export type LocalPTY = {
@@ -33,11 +34,7 @@ function num(value: unknown) {
}
function numberFromTitle(title: string) {
- const match = title.match(/^Terminal (\d+)$/)
- if (!match) return
- const value = Number(match[1])
- if (!Number.isFinite(value) || value <= 0) return
- return value
+ return titleNumber(title, MAX_TERMINAL_SESSIONS)
}
function pty(value: unknown): LocalPTY | undefined {
@@ -202,13 +199,13 @@ function createWorkspaceTerminalSession(sdk: ReturnType<typeof useSDK>, dir: str
const nextNumber = pickNextTerminalNumber()
sdk.client.pty
- .create({ title: `Terminal ${nextNumber}` })
+ .create({ title: defaultTitle(nextNumber) })
.then((pty: { data?: { id?: string; title?: string } }) => {
const id = pty.data?.id
if (!id) return
const newTerminal = {
id,
- title: pty.data?.title ?? "Terminal",
+ title: pty.data?.title ?? defaultTitle(nextNumber),
titleNumber: nextNumber,
}
setStore("all", store.all.length, newTerminal)