diff options
| author | Adam <[email protected]> | 2026-03-13 06:48:38 -0500 |
|---|---|---|
| committer | GitHub <[email protected]> | 2026-03-13 06:48:38 -0500 |
| commit | 05cb3c87ca387be41aceb5ccad978c6848a56f70 (patch) | |
| tree | 2b592d2aa90d0fdb7ea72aa392507e2277b92ba5 /packages/app/src/context | |
| parent | 270cb0b8b4265ac0965ac8b94a58a3bca86fa558 (diff) | |
| download | opencode-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.tsx | 61 | ||||
| -rw-r--r-- | packages/app/src/context/file.tsx | 6 | ||||
| -rw-r--r-- | packages/app/src/context/global-sdk.tsx | 6 | ||||
| -rw-r--r-- | packages/app/src/context/global-sync.tsx | 1 | ||||
| -rw-r--r-- | packages/app/src/context/global-sync/bootstrap.ts | 2 | ||||
| -rw-r--r-- | packages/app/src/context/global-sync/child-store.test.ts | 1 | ||||
| -rw-r--r-- | packages/app/src/context/global-sync/child-store.ts | 9 | ||||
| -rw-r--r-- | packages/app/src/context/terminal-title.ts | 51 | ||||
| -rw-r--r-- | packages/app/src/context/terminal.tsx | 11 |
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) |
