From 233d003b4926ec615ff15c1ddd54a1719a62ef13 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Tue, 20 Jan 2026 17:56:53 -0600 Subject: [PATCH] wip(app): i18n --- .../app/src/components/dialog-settings.tsx | 9 +- .../app/src/components/settings-agents.tsx | 7 +- .../app/src/components/settings-commands.tsx | 7 +- .../app/src/components/settings-general.tsx | 94 +++++++++--- .../app/src/components/settings-keybinds.tsx | 49 +++++-- packages/app/src/components/settings-mcp.tsx | 7 +- .../app/src/components/settings-models.tsx | 7 +- .../src/components/settings-permissions.tsx | 134 ++++++++++++++---- .../app/src/components/settings-providers.tsx | 7 +- packages/app/src/i18n/en.ts | 103 ++++++++++++++ packages/app/src/i18n/zh.ts | 103 ++++++++++++++ 11 files changed, 451 insertions(+), 76 deletions(-) diff --git a/packages/app/src/components/dialog-settings.tsx b/packages/app/src/components/dialog-settings.tsx index 5ef89b8bf..1e9575cb2 100644 --- a/packages/app/src/components/dialog-settings.tsx +++ b/packages/app/src/components/dialog-settings.tsx @@ -2,6 +2,7 @@ import { Component } from "solid-js" import { Dialog } from "@opencode-ai/ui/dialog" import { Tabs } from "@opencode-ai/ui/tabs" import { Icon } from "@opencode-ai/ui/icon" +import { useLanguage } from "@/context/language" import { SettingsGeneral } from "./settings-general" import { SettingsKeybinds } from "./settings-keybinds" import { SettingsPermissions } from "./settings-permissions" @@ -12,6 +13,8 @@ import { SettingsCommands } from "./settings-commands" import { SettingsMcp } from "./settings-mcp" export const DialogSettings: Component = () => { + const language = useLanguage() + return ( @@ -26,15 +29,15 @@ export const DialogSettings: Component = () => { "padding-bottom": "12px", }} > - Desktop + {language.t("settings.section.desktop")}
- General + {language.t("settings.tab.general")} - Shortcuts + {language.t("settings.tab.shortcuts")}
diff --git a/packages/app/src/components/settings-agents.tsx b/packages/app/src/components/settings-agents.tsx index 892be152b..e68f1e59c 100644 --- a/packages/app/src/components/settings-agents.tsx +++ b/packages/app/src/components/settings-agents.tsx @@ -1,11 +1,14 @@ import { Component } from "solid-js" +import { useLanguage } from "@/context/language" export const SettingsAgents: Component = () => { + const language = useLanguage() + return (
-

Agents

-

Agent settings will be configurable here.

+

{language.t("settings.agents.title")}

+

{language.t("settings.agents.description")}

) diff --git a/packages/app/src/components/settings-commands.tsx b/packages/app/src/components/settings-commands.tsx index e98c0eeb0..cf796d0aa 100644 --- a/packages/app/src/components/settings-commands.tsx +++ b/packages/app/src/components/settings-commands.tsx @@ -1,11 +1,14 @@ import { Component } from "solid-js" +import { useLanguage } from "@/context/language" export const SettingsCommands: Component = () => { + const language = useLanguage() + return (
-

Commands

-

Command settings will be configurable here.

+

{language.t("settings.commands.title")}

+

{language.t("settings.commands.description")}

) diff --git a/packages/app/src/components/settings-general.tsx b/packages/app/src/components/settings-general.tsx index e8749cbde..5f3519a74 100644 --- a/packages/app/src/components/settings-general.tsx +++ b/packages/app/src/components/settings-general.tsx @@ -2,22 +2,33 @@ import { Component, createMemo, type JSX } from "solid-js" import { Select } from "@opencode-ai/ui/select" import { Switch } from "@opencode-ai/ui/switch" import { useTheme, type ColorScheme } from "@opencode-ai/ui/theme" +import { useLanguage } from "@/context/language" import { useSettings, monoFontFamily } from "@/context/settings" import { playSound, SOUND_OPTIONS } from "@/utils/sound" export const SettingsGeneral: Component = () => { const theme = useTheme() + const language = useLanguage() const settings = useSettings() const themeOptions = createMemo(() => Object.entries(theme.themes()).map(([id, def]) => ({ id, name: def.name ?? id })), ) - const colorSchemeOptions: { value: ColorScheme; label: string }[] = [ - { value: "system", label: "System setting" }, - { value: "light", label: "Light" }, - { value: "dark", label: "Dark" }, - ] + const colorSchemeOptions = createMemo( + (): { value: ColorScheme; label: string }[] => [ + { value: "system", label: language.t("theme.scheme.system") }, + { value: "light", label: language.t("theme.scheme.light") }, + { value: "dark", label: language.t("theme.scheme.dark") }, + ], + ) + + const languageOptions = createMemo(() => + language.locales.map((locale) => ({ + value: locale, + label: language.label(locale), + })), + ) const fontOptions = [ { value: "ibm-plex-mono", label: "IBM Plex Mono" }, @@ -45,20 +56,39 @@ export const SettingsGeneral: Component = () => { }} >
-

General

+

{language.t("settings.tab.general")}

{/* Appearance Section */}
-

Appearance

+

{language.t("settings.general.section.appearance")}

- + o.value === theme.colorScheme())} value={(o) => o.value} label={(o) => o.label} onSelect={(option) => option && theme.setColorScheme(option.value)} @@ -74,12 +104,12 @@ export const SettingsGeneral: Component = () => { - Customise how OpenCode is themed.{" "} + {language.t("settings.general.row.theme.description")} {" "} - Learn more + {language.t("common.learnMore")} } @@ -104,7 +134,10 @@ export const SettingsGeneral: Component = () => { /> - + o.id === settings.sounds.agent())} @@ -179,7 +221,10 @@ export const SettingsGeneral: Component = () => { /> - + o.id === settings.sounds.errors())} diff --git a/packages/app/src/components/settings-keybinds.tsx b/packages/app/src/components/settings-keybinds.tsx index bac727cd7..13a0042ff 100644 --- a/packages/app/src/components/settings-keybinds.tsx +++ b/packages/app/src/components/settings-keybinds.tsx @@ -2,6 +2,7 @@ import { Component, For, Show, createMemo, createSignal, onCleanup, onMount } fr import { Button } from "@opencode-ai/ui/button" import { showToast } from "@opencode-ai/ui/toast" import { formatKeybind, parseKeybind, useCommand } from "@/context/command" +import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform) @@ -17,6 +18,23 @@ type KeybindMeta = { const GROUPS: KeybindGroup[] = ["General", "Session", "Navigation", "Model and agent", "Terminal", "Prompt"] +type GroupKey = + | "settings.shortcuts.group.general" + | "settings.shortcuts.group.session" + | "settings.shortcuts.group.navigation" + | "settings.shortcuts.group.modelAndAgent" + | "settings.shortcuts.group.terminal" + | "settings.shortcuts.group.prompt" + +const groupKey: Record = { + General: "settings.shortcuts.group.general", + Session: "settings.shortcuts.group.session", + Navigation: "settings.shortcuts.group.navigation", + "Model and agent": "settings.shortcuts.group.modelAndAgent", + Terminal: "settings.shortcuts.group.terminal", + Prompt: "settings.shortcuts.group.prompt", +} + function groupFor(id: string): KeybindGroup { if (id === PALETTE_ID) return "General" if (id.startsWith("terminal.")) return "Terminal" @@ -86,6 +104,7 @@ function signatures(config: string | undefined) { export const SettingsKeybinds: Component = () => { const command = useCommand() + const language = useLanguage() const settings = useSettings() const [active, setActive] = createSignal(null) @@ -117,12 +136,16 @@ export const SettingsKeybinds: Component = () => { const resetAll = () => { stop() settings.keybinds.resetAll() - showToast({ title: "Shortcuts reset", description: "Keyboard shortcuts have been reset to defaults." }) + showToast({ + title: language.t("settings.shortcuts.reset.toast.title"), + description: language.t("settings.shortcuts.reset.toast.description"), + }) } const list = createMemo(() => { + language.locale() const out = new Map() - out.set(PALETTE_ID, { title: "Command palette", group: "General" }) + out.set(PALETTE_ID, { title: language.t("command.palette"), group: "General" }) for (const opt of command.catalog) { if (opt.id.startsWith("suggested.")) continue @@ -188,7 +211,7 @@ export const SettingsKeybinds: Component = () => { const palette = settings.keybinds.get(PALETTE_ID) ?? DEFAULT_PALETTE_KEYBIND for (const sig of signatures(palette)) { - add(sig, { id: PALETTE_ID, title: "Command palette" }) + add(sig, { id: PALETTE_ID, title: title(PALETTE_ID) }) } const valueFor = (id: string) => { @@ -258,8 +281,11 @@ export const SettingsKeybinds: Component = () => { if (conflicts.size > 0) { showToast({ - title: "Shortcut already in use", - description: `${formatKeybind(next)} is already assigned to ${[...conflicts.values()].join(", ")}.`, + title: language.t("settings.shortcuts.conflict.title"), + description: language.t("settings.shortcuts.conflict.description", { + keybind: formatKeybind(next), + titles: [...conflicts.values()].join(", "), + }), }) return } @@ -288,9 +314,9 @@ export const SettingsKeybinds: Component = () => { }} >
-

Keyboard shortcuts

+

{language.t("settings.shortcuts.title")}

@@ -300,7 +326,7 @@ export const SettingsKeybinds: Component = () => { {(group) => ( 0}>
-

{group}

+

{language.t(groupKey[group])}

{(id) => ( @@ -316,8 +342,11 @@ export const SettingsKeybinds: Component = () => { }} onClick={() => start(id)} > - - Press keys + + {language.t("settings.shortcuts.pressKeys")}
diff --git a/packages/app/src/components/settings-mcp.tsx b/packages/app/src/components/settings-mcp.tsx index ea6bf350f..928464a51 100644 --- a/packages/app/src/components/settings-mcp.tsx +++ b/packages/app/src/components/settings-mcp.tsx @@ -1,11 +1,14 @@ import { Component } from "solid-js" +import { useLanguage } from "@/context/language" export const SettingsMcp: Component = () => { + const language = useLanguage() + return (
-

MCP

-

MCP settings will be configurable here.

+

{language.t("settings.mcp.title")}

+

{language.t("settings.mcp.description")}

) diff --git a/packages/app/src/components/settings-models.tsx b/packages/app/src/components/settings-models.tsx index 5fbeb144e..6a636879d 100644 --- a/packages/app/src/components/settings-models.tsx +++ b/packages/app/src/components/settings-models.tsx @@ -1,11 +1,14 @@ import { Component } from "solid-js" +import { useLanguage } from "@/context/language" export const SettingsModels: Component = () => { + const language = useLanguage() + return (
-

Models

-

Model settings will be configurable here.

+

{language.t("settings.models.title")}

+

{language.t("settings.models.description")}

) diff --git a/packages/app/src/components/settings-permissions.tsx b/packages/app/src/components/settings-permissions.tsx index d0551d247..1381515f5 100644 --- a/packages/app/src/components/settings-permissions.tsx +++ b/packages/app/src/components/settings-permissions.tsx @@ -2,6 +2,7 @@ import { Select } from "@opencode-ai/ui/select" import { showToast } from "@opencode-ai/ui/toast" import { Component, For, createMemo, type JSX } from "solid-js" import { useGlobalSync } from "@/context/global-sync" +import { useLanguage } from "@/context/language" type PermissionAction = "allow" | "ask" | "deny" @@ -15,30 +16,94 @@ type PermissionItem = { description: string } -const ACTIONS: Array<{ value: PermissionAction; label: string }> = [ - { value: "allow", label: "Allow" }, - { value: "ask", label: "Ask" }, - { value: "deny", label: "Deny" }, -] +const ACTIONS = [ + { value: "allow", label: "settings.permissions.action.allow" }, + { value: "ask", label: "settings.permissions.action.ask" }, + { value: "deny", label: "settings.permissions.action.deny" }, +] as const -const ITEMS: PermissionItem[] = [ - { id: "read", title: "Read", description: "Reading a file (matches the file path)" }, - { id: "edit", title: "Edit", description: "Modify files, including edits, writes, patches, and multi-edits" }, - { id: "glob", title: "Glob", description: "Match files using glob patterns" }, - { id: "grep", title: "Grep", description: "Search file contents using regular expressions" }, - { id: "list", title: "List", description: "List files within a directory" }, - { id: "bash", title: "Bash", description: "Run shell commands" }, - { id: "task", title: "Task", description: "Launch sub-agents" }, - { id: "skill", title: "Skill", description: "Load a skill by name" }, - { id: "lsp", title: "LSP", description: "Run language server queries" }, - { id: "todoread", title: "Todo Read", description: "Read the todo list" }, - { id: "todowrite", title: "Todo Write", description: "Update the todo list" }, - { id: "webfetch", title: "Web Fetch", description: "Fetch content from a URL" }, - { id: "websearch", title: "Web Search", description: "Search the web" }, - { id: "codesearch", title: "Code Search", description: "Search code on the web" }, - { id: "external_directory", title: "External Directory", description: "Access files outside the project directory" }, - { id: "doom_loop", title: "Doom Loop", description: "Detect repeated tool calls with identical input" }, -] +const ITEMS = [ + { + id: "read", + title: "settings.permissions.tool.read.title", + description: "settings.permissions.tool.read.description", + }, + { + id: "edit", + title: "settings.permissions.tool.edit.title", + description: "settings.permissions.tool.edit.description", + }, + { + id: "glob", + title: "settings.permissions.tool.glob.title", + description: "settings.permissions.tool.glob.description", + }, + { + id: "grep", + title: "settings.permissions.tool.grep.title", + description: "settings.permissions.tool.grep.description", + }, + { + id: "list", + title: "settings.permissions.tool.list.title", + description: "settings.permissions.tool.list.description", + }, + { + id: "bash", + title: "settings.permissions.tool.bash.title", + description: "settings.permissions.tool.bash.description", + }, + { + id: "task", + title: "settings.permissions.tool.task.title", + description: "settings.permissions.tool.task.description", + }, + { + id: "skill", + title: "settings.permissions.tool.skill.title", + description: "settings.permissions.tool.skill.description", + }, + { + id: "lsp", + title: "settings.permissions.tool.lsp.title", + description: "settings.permissions.tool.lsp.description", + }, + { + id: "todoread", + title: "settings.permissions.tool.todoread.title", + description: "settings.permissions.tool.todoread.description", + }, + { + id: "todowrite", + title: "settings.permissions.tool.todowrite.title", + description: "settings.permissions.tool.todowrite.description", + }, + { + id: "webfetch", + title: "settings.permissions.tool.webfetch.title", + description: "settings.permissions.tool.webfetch.description", + }, + { + id: "websearch", + title: "settings.permissions.tool.websearch.title", + description: "settings.permissions.tool.websearch.description", + }, + { + id: "codesearch", + title: "settings.permissions.tool.codesearch.title", + description: "settings.permissions.tool.codesearch.description", + }, + { + id: "external_directory", + title: "settings.permissions.tool.external_directory.title", + description: "settings.permissions.tool.external_directory.description", + }, + { + id: "doom_loop", + title: "settings.permissions.tool.doom_loop.title", + description: "settings.permissions.tool.doom_loop.description", + }, +] as const const VALID_ACTIONS = new Set(["allow", "ask", "deny"]) @@ -67,6 +132,15 @@ function getRuleDefault(value: unknown): PermissionAction | undefined { export const SettingsPermissions: Component = () => { const globalSync = useGlobalSync() + const language = useLanguage() + + const actions = createMemo( + (): Array<{ value: PermissionAction; label: string }> => + ACTIONS.map((action) => ({ + value: action.value, + label: language.t(action.label), + })), + ) const permission = createMemo(() => { return toMap(globalSync.data.config.permission) @@ -95,7 +169,7 @@ export const SettingsPermissions: Component = () => { globalSync.updateConfig({ permission: { [id]: nextValue } }).catch((err: unknown) => { globalSync.set("config", "permission", before) const message = err instanceof Error ? err.message : String(err) - showToast({ title: "Failed to update permissions", description: message }) + showToast({ title: language.t("settings.permissions.toast.updateFailed.title"), description: message }) }) } @@ -109,21 +183,21 @@ export const SettingsPermissions: Component = () => { }} >
-

Permissions

-

Control what tools the server can use by default.

+

{language.t("settings.permissions.title")}

+

{language.t("settings.permissions.description")}

-

Appearance

+

{language.t("settings.permissions.section.tools")}

{(item) => ( - +