feat(ui): localize UI strings
Converts hardcoded UI copy to i18n keys across the app, adds global translation for non-component modules, and splits the English catalog into feature modules with duplicate-key detection.
This commit is contained in:
@@ -3,6 +3,7 @@ import type { Command as SDKCommand } from "@opencode-ai/sdk"
|
||||
import { showAlertDialog, showPromptDialog } from "../stores/alerts"
|
||||
import { activeSessionId, executeCustomCommand } from "../stores/sessions"
|
||||
import { getLogger } from "./logger"
|
||||
import { tGlobal } from "./i18n"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
@@ -17,19 +18,19 @@ export async function promptForCommandArguments(command: SDKCommand): Promise<st
|
||||
}
|
||||
|
||||
try {
|
||||
return await showPromptDialog(`Arguments for /${command.name}`, {
|
||||
title: "Custom command",
|
||||
return await showPromptDialog(tGlobal("commands.custom.argumentsPrompt.message", { name: command.name }), {
|
||||
title: tGlobal("commands.custom.argumentsPrompt.title"),
|
||||
variant: "info",
|
||||
inputLabel: "Arguments",
|
||||
inputPlaceholder: "e.g. foo bar",
|
||||
inputLabel: tGlobal("commands.custom.argumentsPrompt.inputLabel"),
|
||||
inputPlaceholder: tGlobal("commands.custom.argumentsPrompt.inputPlaceholder"),
|
||||
inputDefaultValue: "",
|
||||
confirmLabel: "Run",
|
||||
cancelLabel: "Cancel",
|
||||
confirmLabel: tGlobal("commands.custom.argumentsPrompt.confirmLabel"),
|
||||
cancelLabel: tGlobal("commands.custom.argumentsPrompt.cancelLabel"),
|
||||
})
|
||||
} catch (error) {
|
||||
log.error("Failed to prompt for command arguments", error)
|
||||
showAlertDialog("Failed to open arguments prompt.", {
|
||||
title: "Command arguments",
|
||||
showAlertDialog(tGlobal("commands.custom.argumentsPrompt.openFailed.message"), {
|
||||
title: tGlobal("commands.custom.argumentsPrompt.openFailed.title"),
|
||||
variant: "error",
|
||||
})
|
||||
return null
|
||||
@@ -45,14 +46,14 @@ export function buildCustomCommandEntries(instanceId: string, commands: SDKComma
|
||||
return commands.map((cmd) => ({
|
||||
id: `custom:${instanceId}:${cmd.name}`,
|
||||
label: formatCommandLabel(cmd.name),
|
||||
description: cmd.description ?? "Custom command",
|
||||
description: () => cmd.description ?? tGlobal("commands.custom.entries.descriptionFallback"),
|
||||
category: "Custom Commands",
|
||||
keywords: [cmd.name, ...(cmd.description ? cmd.description.split(/\s+/).filter(Boolean) : [])],
|
||||
action: async () => {
|
||||
const sessionId = activeSessionId().get(instanceId)
|
||||
if (!sessionId || sessionId === "info") {
|
||||
showAlertDialog("Select a session before running a custom command.", {
|
||||
title: "Session required",
|
||||
showAlertDialog(tGlobal("commands.custom.sessionRequired.message"), {
|
||||
title: tGlobal("commands.custom.sessionRequired.title"),
|
||||
variant: "warning",
|
||||
})
|
||||
return
|
||||
@@ -65,8 +66,8 @@ export function buildCustomCommandEntries(instanceId: string, commands: SDKComma
|
||||
await executeCustomCommand(instanceId, sessionId, cmd.name, args)
|
||||
} catch (error) {
|
||||
log.error("Failed to run custom command", error)
|
||||
showAlertDialog("Failed to run custom command. Check the console for details.", {
|
||||
title: "Command failed",
|
||||
showAlertDialog(tGlobal("commands.custom.runFailed.message"), {
|
||||
title: tGlobal("commands.custom.runFailed.title"),
|
||||
variant: "error",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -6,14 +6,20 @@ export interface KeyboardShortcut {
|
||||
alt?: boolean
|
||||
}
|
||||
|
||||
export type Resolvable<T> = T | (() => T)
|
||||
|
||||
export function resolveResolvable<T>(value: Resolvable<T>): T {
|
||||
return typeof value === "function" ? (value as () => T)() : value
|
||||
}
|
||||
|
||||
export interface Command {
|
||||
id: string
|
||||
label: string | (() => string)
|
||||
description: string
|
||||
keywords?: string[]
|
||||
label: Resolvable<string>
|
||||
description: Resolvable<string>
|
||||
keywords?: Resolvable<string[]>
|
||||
shortcut?: KeyboardShortcut
|
||||
action: () => void | Promise<void>
|
||||
category?: string
|
||||
category?: Resolvable<string>
|
||||
}
|
||||
|
||||
export function createCommandRegistry() {
|
||||
@@ -47,11 +53,15 @@ export function createCommandRegistry() {
|
||||
|
||||
const lowerQuery = query.toLowerCase()
|
||||
return getAll().filter((cmd) => {
|
||||
const label = typeof cmd.label === "function" ? cmd.label() : cmd.label
|
||||
const label = resolveResolvable(cmd.label)
|
||||
const description = resolveResolvable(cmd.description)
|
||||
const keywords = cmd.keywords ? resolveResolvable(cmd.keywords) : undefined
|
||||
const category = cmd.category ? resolveResolvable(cmd.category) : undefined
|
||||
const labelMatch = label.toLowerCase().includes(lowerQuery)
|
||||
const descMatch = cmd.description.toLowerCase().includes(lowerQuery)
|
||||
const keywordMatch = cmd.keywords?.some((k) => k.toLowerCase().includes(lowerQuery))
|
||||
return labelMatch || descMatch || keywordMatch
|
||||
const descMatch = description.toLowerCase().includes(lowerQuery)
|
||||
const keywordMatch = keywords?.some((k) => k.toLowerCase().includes(lowerQuery))
|
||||
const categoryMatch = category?.toLowerCase().includes(lowerQuery)
|
||||
return labelMatch || descMatch || keywordMatch || categoryMatch
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,17 @@ import { cleanupBlankSessions } from "../../stores/session-state"
|
||||
import { getLogger } from "../logger"
|
||||
import { requestData } from "../opencode-api"
|
||||
import { emitSessionSidebarRequest } from "../session-sidebar-events"
|
||||
import { tGlobal } from "../i18n"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
function splitKeywords(key: string): string[] {
|
||||
return tGlobal(key)
|
||||
.split(",")
|
||||
.map((value) => value.trim())
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
|
||||
export interface UseCommandsOptions {
|
||||
preferences: Accessor<Preferences>
|
||||
@@ -61,20 +69,20 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "new-instance",
|
||||
label: "New Instance",
|
||||
description: "Open folder picker to create new instance",
|
||||
label: () => tGlobal("commands.newInstance.label"),
|
||||
description: () => tGlobal("commands.newInstance.description"),
|
||||
category: "Instance",
|
||||
keywords: ["folder", "project", "workspace"],
|
||||
keywords: () => splitKeywords("commands.newInstance.keywords"),
|
||||
shortcut: { key: "N", meta: true },
|
||||
action: options.handleNewInstanceRequest,
|
||||
})
|
||||
|
||||
commandRegistry.register({
|
||||
id: "close-instance",
|
||||
label: "Close Instance",
|
||||
description: "Stop current instance's server",
|
||||
label: () => tGlobal("commands.closeInstance.label"),
|
||||
description: () => tGlobal("commands.closeInstance.description"),
|
||||
category: "Instance",
|
||||
keywords: ["stop", "quit", "close"],
|
||||
keywords: () => splitKeywords("commands.closeInstance.keywords"),
|
||||
shortcut: { key: "W", meta: true },
|
||||
action: async () => {
|
||||
const instance = activeInstance()
|
||||
@@ -85,10 +93,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "instance-next",
|
||||
label: "Next Instance",
|
||||
description: "Cycle to next instance tab",
|
||||
label: () => tGlobal("commands.nextInstance.label"),
|
||||
description: () => tGlobal("commands.nextInstance.description"),
|
||||
category: "Instance",
|
||||
keywords: ["switch", "navigate"],
|
||||
keywords: () => splitKeywords("commands.nextInstance.keywords"),
|
||||
shortcut: { key: "]", meta: true },
|
||||
action: () => {
|
||||
const ids = Array.from(instances().keys())
|
||||
@@ -101,10 +109,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "instance-prev",
|
||||
label: "Previous Instance",
|
||||
description: "Cycle to previous instance tab",
|
||||
label: () => tGlobal("commands.previousInstance.label"),
|
||||
description: () => tGlobal("commands.previousInstance.description"),
|
||||
category: "Instance",
|
||||
keywords: ["switch", "navigate"],
|
||||
keywords: () => splitKeywords("commands.previousInstance.keywords"),
|
||||
shortcut: { key: "[", meta: true },
|
||||
action: () => {
|
||||
const ids = Array.from(instances().keys())
|
||||
@@ -117,10 +125,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "new-session",
|
||||
label: "New Session",
|
||||
description: "Create a new parent session",
|
||||
label: () => tGlobal("commands.newSession.label"),
|
||||
description: () => tGlobal("commands.newSession.description"),
|
||||
category: "Session",
|
||||
keywords: ["create", "start"],
|
||||
keywords: () => splitKeywords("commands.newSession.keywords"),
|
||||
shortcut: { key: "N", meta: true, shift: true },
|
||||
action: async () => {
|
||||
const instance = activeInstance()
|
||||
@@ -131,10 +139,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "close-session",
|
||||
label: "Close Session",
|
||||
description: "Close current parent session",
|
||||
label: () => tGlobal("commands.closeSession.label"),
|
||||
description: () => tGlobal("commands.closeSession.description"),
|
||||
category: "Session",
|
||||
keywords: ["close", "stop"],
|
||||
keywords: () => splitKeywords("commands.closeSession.keywords"),
|
||||
shortcut: { key: "W", meta: true, shift: true },
|
||||
action: async () => {
|
||||
const instance = activeInstance()
|
||||
@@ -146,10 +154,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "cleanup-blank-sessions",
|
||||
label: "Scrub Sessions",
|
||||
description: "Remove empty sessions, subagent sessions that have completed their primary task, and extraneous forked sessions.",
|
||||
label: () => tGlobal("commands.scrubSessions.label"),
|
||||
description: () => tGlobal("commands.scrubSessions.description"),
|
||||
category: "Session",
|
||||
keywords: ["cleanup", "blank", "empty", "sessions", "remove", "delete", "scrub"],
|
||||
keywords: () => splitKeywords("commands.scrubSessions.keywords"),
|
||||
action: async () => {
|
||||
const instance = activeInstance()
|
||||
if (!instance) return
|
||||
@@ -159,10 +167,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "switch-to-info",
|
||||
label: "Instance Info",
|
||||
description: "Open the instance overview for logs and status",
|
||||
label: () => tGlobal("commands.instanceInfo.label"),
|
||||
description: () => tGlobal("commands.instanceInfo.description"),
|
||||
category: "Instance",
|
||||
keywords: ["info", "logs", "console", "output"],
|
||||
keywords: () => splitKeywords("commands.instanceInfo.keywords"),
|
||||
shortcut: { key: "L", meta: true, shift: true },
|
||||
action: () => {
|
||||
const instance = activeInstance()
|
||||
@@ -172,10 +180,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "session-next",
|
||||
label: "Next Session",
|
||||
description: "Cycle to next session tab",
|
||||
label: () => tGlobal("commands.nextSession.label"),
|
||||
description: () => tGlobal("commands.nextSession.description"),
|
||||
category: "Session",
|
||||
keywords: ["switch", "navigate"],
|
||||
keywords: () => splitKeywords("commands.nextSession.keywords"),
|
||||
shortcut: { key: "]", meta: true, shift: true },
|
||||
action: () => {
|
||||
const instanceId = activeInstanceId()
|
||||
@@ -197,10 +205,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "session-prev",
|
||||
label: "Previous Session",
|
||||
description: "Cycle to previous session tab",
|
||||
label: () => tGlobal("commands.previousSession.label"),
|
||||
description: () => tGlobal("commands.previousSession.description"),
|
||||
category: "Session",
|
||||
keywords: ["switch", "navigate"],
|
||||
keywords: () => splitKeywords("commands.previousSession.keywords"),
|
||||
shortcut: { key: "[", meta: true, shift: true },
|
||||
action: () => {
|
||||
const instanceId = activeInstanceId()
|
||||
@@ -223,10 +231,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "compact",
|
||||
label: "Compact Session",
|
||||
description: "Summarize and compact the current session",
|
||||
label: () => tGlobal("commands.compactSession.label"),
|
||||
description: () => tGlobal("commands.compactSession.description"),
|
||||
category: "Session",
|
||||
keywords: ["/compact", "summarize", "compress"],
|
||||
keywords: () => ["/compact", ...splitKeywords("commands.compactSession.keywords")],
|
||||
action: async () => {
|
||||
const instance = activeInstance()
|
||||
const sessionId = activeSessionIdForInstance()
|
||||
@@ -247,9 +255,9 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
)
|
||||
} catch (error) {
|
||||
log.error("Failed to compact session", error)
|
||||
const message = error instanceof Error ? error.message : "Failed to compact session"
|
||||
showAlertDialog(`Compact failed: ${message}`, {
|
||||
title: "Compact failed",
|
||||
const message = error instanceof Error ? error.message : tGlobal("commands.compactSession.errorFallback")
|
||||
showAlertDialog(tGlobal("commands.compactSession.alert.message", { message }), {
|
||||
title: tGlobal("commands.compactSession.alert.title"),
|
||||
variant: "error",
|
||||
})
|
||||
}
|
||||
@@ -275,10 +283,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "undo",
|
||||
label: "Undo Last Message",
|
||||
description: "Revert the last message",
|
||||
label: () => tGlobal("commands.undoLastMessage.label"),
|
||||
description: () => tGlobal("commands.undoLastMessage.description"),
|
||||
category: "Session",
|
||||
keywords: ["/undo", "revert", "undo"],
|
||||
keywords: () => ["/undo", ...splitKeywords("commands.undoLastMessage.keywords")],
|
||||
action: async () => {
|
||||
const instance = activeInstance()
|
||||
const sessionId = activeSessionIdForInstance()
|
||||
@@ -320,8 +328,8 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
}
|
||||
|
||||
if (!messageID) {
|
||||
showAlertDialog("Nothing to undo", {
|
||||
title: "No actions to undo",
|
||||
showAlertDialog(tGlobal("commands.undoLastMessage.none.message"), {
|
||||
title: tGlobal("commands.undoLastMessage.none.title"),
|
||||
variant: "info",
|
||||
})
|
||||
return
|
||||
@@ -351,8 +359,8 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
}
|
||||
} catch (error) {
|
||||
log.error("Failed to revert message", error)
|
||||
showAlertDialog("Failed to revert message", {
|
||||
title: "Undo failed",
|
||||
showAlertDialog(tGlobal("commands.undoLastMessage.failed.message"), {
|
||||
title: tGlobal("commands.undoLastMessage.failed.title"),
|
||||
variant: "error",
|
||||
})
|
||||
}
|
||||
@@ -362,10 +370,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "open-model-selector",
|
||||
label: "Open Model Selector",
|
||||
description: "Choose a different model",
|
||||
label: () => tGlobal("commands.openModelSelector.label"),
|
||||
description: () => tGlobal("commands.openModelSelector.description"),
|
||||
category: "Agent & Model",
|
||||
keywords: ["model", "llm", "ai"],
|
||||
keywords: () => splitKeywords("commands.openModelSelector.keywords"),
|
||||
shortcut: { key: "M", meta: true, shift: true },
|
||||
action: () => {
|
||||
const instance = activeInstance()
|
||||
@@ -376,10 +384,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "open-variant-selector",
|
||||
label: "Select Model Variant",
|
||||
description: "Choose a thinking effort for the current model",
|
||||
label: () => tGlobal("commands.selectModelVariant.label"),
|
||||
description: () => tGlobal("commands.selectModelVariant.description"),
|
||||
category: "Agent & Model",
|
||||
keywords: ["variant", "thinking", "reasoning", "effort"],
|
||||
keywords: () => splitKeywords("commands.selectModelVariant.keywords"),
|
||||
shortcut: { key: "T", meta: true, shift: true },
|
||||
action: () => {
|
||||
const instance = activeInstance()
|
||||
@@ -390,10 +398,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "open-agent-selector",
|
||||
label: "Open Agent Selector",
|
||||
description: "Choose a different agent",
|
||||
label: () => tGlobal("commands.openAgentSelector.label"),
|
||||
description: () => tGlobal("commands.openAgentSelector.description"),
|
||||
category: "Agent & Model",
|
||||
keywords: ["agent", "mode"],
|
||||
keywords: () => splitKeywords("commands.openAgentSelector.keywords"),
|
||||
shortcut: { key: "A", meta: true, shift: true },
|
||||
action: () => {
|
||||
const instance = activeInstance()
|
||||
@@ -404,10 +412,10 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "clear-input",
|
||||
label: "Clear Input",
|
||||
description: "Clear the prompt textarea",
|
||||
label: () => tGlobal("commands.clearInput.label"),
|
||||
description: () => tGlobal("commands.clearInput.description"),
|
||||
category: "Input & Focus",
|
||||
keywords: ["clear", "reset"],
|
||||
keywords: () => splitKeywords("commands.clearInput.keywords"),
|
||||
shortcut: { key: "K", meta: true },
|
||||
action: () => {
|
||||
const textarea = findVisiblePromptTextarea()
|
||||
@@ -417,19 +425,19 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "thinking",
|
||||
label: () => `${options.preferences().showThinkingBlocks ? "Hide" : "Show"} Thinking Blocks`,
|
||||
description: "Show/hide AI thinking process",
|
||||
label: () => tGlobal(options.preferences().showThinkingBlocks ? "commands.thinkingBlocks.label.hide" : "commands.thinkingBlocks.label.show"),
|
||||
description: () => tGlobal("commands.thinkingBlocks.description"),
|
||||
category: "System",
|
||||
keywords: ["/thinking", "thinking", "reasoning", "toggle", "show", "hide"],
|
||||
keywords: () => ["/thinking", ...splitKeywords("commands.thinkingBlocks.keywords")],
|
||||
action: options.toggleShowThinkingBlocks,
|
||||
})
|
||||
|
||||
commandRegistry.register({
|
||||
id: "timeline-tools",
|
||||
label: () => `${options.preferences().showTimelineTools ? "Hide" : "Show"} Timeline Tool Calls`,
|
||||
description: "Toggle tool call entries in the message timeline",
|
||||
label: () => tGlobal(options.preferences().showTimelineTools ? "commands.timelineToolCalls.label.hide" : "commands.timelineToolCalls.label.show"),
|
||||
description: () => tGlobal("commands.timelineToolCalls.description"),
|
||||
category: "System",
|
||||
keywords: ["timeline", "tool", "toggle"],
|
||||
keywords: () => splitKeywords("commands.timelineToolCalls.keywords"),
|
||||
action: options.toggleShowTimelineTools,
|
||||
})
|
||||
|
||||
@@ -437,11 +445,12 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
id: "thinking-default-visibility",
|
||||
label: () => {
|
||||
const mode = options.preferences().thinkingBlocksExpansion ?? "expanded"
|
||||
return `Thinking Blocks Default · ${mode === "expanded" ? "Expanded" : "Collapsed"}`
|
||||
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
|
||||
return tGlobal("commands.thinkingBlocksDefault.label", { state })
|
||||
},
|
||||
description: "Toggle whether thinking blocks start expanded",
|
||||
description: () => tGlobal("commands.thinkingBlocksDefault.description"),
|
||||
category: "System",
|
||||
keywords: ["/thinking", "thinking", "reasoning", "expand", "collapse", "default"],
|
||||
keywords: () => ["/thinking", ...splitKeywords("commands.thinkingBlocksDefault.keywords")],
|
||||
action: () => {
|
||||
const mode = options.preferences().thinkingBlocksExpansion ?? "expanded"
|
||||
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
|
||||
@@ -451,19 +460,25 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
|
||||
commandRegistry.register({
|
||||
id: "diff-view-split",
|
||||
label: () => `${(options.preferences().diffViewMode || "split") === "split" ? "✓ " : ""}Use Split Diff View`,
|
||||
description: "Display tool-call diffs side-by-side",
|
||||
label: () => {
|
||||
const prefix = (options.preferences().diffViewMode || "split") === "split" ? "✓ " : ""
|
||||
return `${prefix}${tGlobal("commands.diffViewSplit.label")}`
|
||||
},
|
||||
description: () => tGlobal("commands.diffViewSplit.description"),
|
||||
category: "System",
|
||||
keywords: ["diff", "split", "view"],
|
||||
keywords: () => splitKeywords("commands.diffViewSplit.keywords"),
|
||||
action: () => options.setDiffViewMode("split"),
|
||||
})
|
||||
|
||||
commandRegistry.register({
|
||||
id: "diff-view-unified",
|
||||
label: () => `${(options.preferences().diffViewMode || "split") === "unified" ? "✓ " : ""}Use Unified Diff View`,
|
||||
description: "Display tool-call diffs inline",
|
||||
label: () => {
|
||||
const prefix = (options.preferences().diffViewMode || "split") === "unified" ? "✓ " : ""
|
||||
return `${prefix}${tGlobal("commands.diffViewUnified.label")}`
|
||||
},
|
||||
description: () => tGlobal("commands.diffViewUnified.description"),
|
||||
category: "System",
|
||||
keywords: ["diff", "unified", "view"],
|
||||
keywords: () => splitKeywords("commands.diffViewUnified.keywords"),
|
||||
action: () => options.setDiffViewMode("unified"),
|
||||
})
|
||||
|
||||
@@ -471,11 +486,12 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
id: "tool-output-default-visibility",
|
||||
label: () => {
|
||||
const mode = options.preferences().toolOutputExpansion || "expanded"
|
||||
return `Tool Outputs Default · ${mode === "expanded" ? "Expanded" : "Collapsed"}`
|
||||
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
|
||||
return tGlobal("commands.toolOutputsDefault.label", { state })
|
||||
},
|
||||
description: "Toggle default expansion for tool outputs",
|
||||
description: () => tGlobal("commands.toolOutputsDefault.description"),
|
||||
category: "System",
|
||||
keywords: ["tool", "output", "expand", "collapse"],
|
||||
keywords: () => splitKeywords("commands.toolOutputsDefault.keywords"),
|
||||
action: () => {
|
||||
const mode = options.preferences().toolOutputExpansion || "expanded"
|
||||
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
|
||||
@@ -487,11 +503,12 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
id: "diagnostics-default-visibility",
|
||||
label: () => {
|
||||
const mode = options.preferences().diagnosticsExpansion || "expanded"
|
||||
return `Diagnostics Default · ${mode === "expanded" ? "Expanded" : "Collapsed"}`
|
||||
const state = mode === "expanded" ? tGlobal("commands.common.expanded") : tGlobal("commands.common.collapsed")
|
||||
return tGlobal("commands.diagnosticsDefault.label", { state })
|
||||
},
|
||||
description: "Toggle default expansion for diagnostics output",
|
||||
description: () => tGlobal("commands.diagnosticsDefault.description"),
|
||||
category: "System",
|
||||
keywords: ["diagnostics", "expand", "collapse"],
|
||||
keywords: () => splitKeywords("commands.diagnosticsDefault.keywords"),
|
||||
action: () => {
|
||||
const mode = options.preferences().diagnosticsExpansion || "expanded"
|
||||
const next: ExpansionPreference = mode === "expanded" ? "collapsed" : "expanded"
|
||||
@@ -503,11 +520,12 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
id: "token-usage-visibility",
|
||||
label: () => {
|
||||
const visible = options.preferences().showUsageMetrics ?? true
|
||||
return `Token Usage Display · ${visible ? "Visible" : "Hidden"}`
|
||||
const state = visible ? tGlobal("commands.common.visible") : tGlobal("commands.common.hidden")
|
||||
return tGlobal("commands.tokenUsageDisplay.label", { state })
|
||||
},
|
||||
description: "Show or hide token and cost stats for assistant messages",
|
||||
description: () => tGlobal("commands.tokenUsageDisplay.description"),
|
||||
category: "System",
|
||||
keywords: ["token", "usage", "cost", "stats"],
|
||||
keywords: () => splitKeywords("commands.tokenUsageDisplay.keywords"),
|
||||
action: options.toggleUsageMetrics,
|
||||
})
|
||||
|
||||
@@ -515,21 +533,21 @@ export function useCommands(options: UseCommandsOptions) {
|
||||
id: "auto-cleanup-blank-sessions",
|
||||
label: () => {
|
||||
const enabled = options.preferences().autoCleanupBlankSessions
|
||||
return `Auto-Cleanup Blank Sessions · ${enabled ? "Enabled" : "Disabled"}`
|
||||
const state = enabled ? tGlobal("commands.common.enabled") : tGlobal("commands.common.disabled")
|
||||
return tGlobal("commands.autoCleanupBlankSessions.label", { state })
|
||||
},
|
||||
description: "Automatically clean up blank sessions when creating new ones",
|
||||
description: () => tGlobal("commands.autoCleanupBlankSessions.description"),
|
||||
category: "System",
|
||||
keywords: ["auto", "cleanup", "blank", "sessions", "toggle"],
|
||||
keywords: () => splitKeywords("commands.autoCleanupBlankSessions.keywords"),
|
||||
action: options.toggleAutoCleanupBlankSessions,
|
||||
})
|
||||
|
||||
commandRegistry.register({
|
||||
id: "help",
|
||||
label: "Show Help",
|
||||
|
||||
description: "Display keyboard shortcuts and help",
|
||||
label: () => tGlobal("commands.showHelp.label"),
|
||||
description: () => tGlobal("commands.showHelp.description"),
|
||||
category: "System",
|
||||
keywords: ["/help", "shortcuts", "help"],
|
||||
keywords: () => ["/help", ...splitKeywords("commands.showHelp.keywords")],
|
||||
action: () => {
|
||||
log.info("Show help modal (not implemented)")
|
||||
},
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { createContext, createMemo, createSignal, onMount, useContext } from "solid-js"
|
||||
import { createContext, createEffect, createMemo, createSignal, onCleanup, onMount, useContext } from "solid-js"
|
||||
import type { ParentComponent } from "solid-js"
|
||||
import { useConfig } from "../../stores/preferences"
|
||||
import { enMessages } from "./messages/en"
|
||||
import { enMessages } from "./messages/en/index"
|
||||
|
||||
type Messages = Record<string, string>
|
||||
|
||||
export type TranslateParams = Record<string, unknown>
|
||||
|
||||
export type Locale = "en"
|
||||
|
||||
const SUPPORTED_LOCALES: readonly Locale[] = ["en"] as const
|
||||
@@ -57,9 +59,25 @@ function interpolate(template: string, params?: Record<string, unknown>): string
|
||||
})
|
||||
}
|
||||
|
||||
function translateFrom(messages: Messages, key: string, params?: TranslateParams): string {
|
||||
const current = messages[key]
|
||||
const fallback = enMessages[key as keyof typeof enMessages]
|
||||
const template = current ?? fallback ?? key
|
||||
return interpolate(template, params)
|
||||
}
|
||||
|
||||
const [globalRevision, setGlobalRevision] = createSignal(0)
|
||||
const initialGlobalLocale: Locale = detectNavigatorLocale() ?? "en"
|
||||
let globalMessages: Messages = messagesByLocale[initialGlobalLocale]
|
||||
|
||||
export function tGlobal(key: string, params?: TranslateParams): string {
|
||||
globalRevision()
|
||||
return translateFrom(globalMessages, key, params)
|
||||
}
|
||||
|
||||
export interface I18nContextValue {
|
||||
locale: () => Locale
|
||||
t: (key: string, params?: Record<string, unknown>) => string
|
||||
t: (key: string, params?: TranslateParams) => string
|
||||
}
|
||||
|
||||
const I18nContext = createContext<I18nContextValue>()
|
||||
@@ -68,6 +86,8 @@ export const I18nProvider: ParentComponent = (props) => {
|
||||
const { preferences } = useConfig()
|
||||
const [detectedLocale, setDetectedLocale] = createSignal<Locale>("en")
|
||||
|
||||
const previousMessages = globalMessages
|
||||
|
||||
onMount(() => {
|
||||
const detected = detectNavigatorLocale()
|
||||
if (detected) setDetectedLocale(detected)
|
||||
@@ -80,13 +100,20 @@ export const I18nProvider: ParentComponent = (props) => {
|
||||
|
||||
const messages = createMemo<Messages>(() => messagesByLocale[locale()])
|
||||
|
||||
function t(key: string, params?: Record<string, unknown>): string {
|
||||
const current = messages()[key]
|
||||
const fallback = enMessages[key as keyof typeof enMessages]
|
||||
const template = current ?? fallback ?? key
|
||||
return interpolate(template, params)
|
||||
function t(key: string, params?: TranslateParams): string {
|
||||
return translateFrom(messages(), key, params)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
globalMessages = messages()
|
||||
setGlobalRevision((value) => value + 1)
|
||||
})
|
||||
|
||||
onCleanup(() => {
|
||||
globalMessages = previousMessages
|
||||
setGlobalRevision((value) => value + 1)
|
||||
})
|
||||
|
||||
const value: I18nContextValue = {
|
||||
locale,
|
||||
t,
|
||||
|
||||
6
packages/ui/src/lib/i18n/messages/en/advancedSettings.ts
Normal file
6
packages/ui/src/lib/i18n/messages/en/advancedSettings.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const advancedSettingsMessages = {
|
||||
"advancedSettings.title": "Advanced Settings",
|
||||
"advancedSettings.environmentVariables.title": "Environment Variables",
|
||||
"advancedSettings.environmentVariables.subtitle": "Applied whenever a new OpenCode instance starts",
|
||||
"advancedSettings.actions.close": "Close",
|
||||
} as const
|
||||
29
packages/ui/src/lib/i18n/messages/en/app.ts
Normal file
29
packages/ui/src/lib/i18n/messages/en/app.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
export const appMessages = {
|
||||
"app.launchError.title": "Unable to launch OpenCode",
|
||||
"app.launchError.description": "We couldn't start the selected OpenCode binary. Review the error output below or choose a different binary from Advanced Settings.",
|
||||
"app.launchError.binaryPathLabel": "Binary path",
|
||||
"app.launchError.errorOutputLabel": "Error output",
|
||||
"app.launchError.openAdvancedSettings": "Open Advanced Settings",
|
||||
"app.launchError.close": "Close",
|
||||
"app.launchError.closeTitle": "Close (Esc)",
|
||||
"app.launchError.fallbackMessage": "Failed to launch workspace",
|
||||
|
||||
"app.stopInstance.confirmMessage": "Stop OpenCode instance? This will stop the server.",
|
||||
"app.stopInstance.title": "Stop instance",
|
||||
"app.stopInstance.confirmLabel": "Stop",
|
||||
"app.stopInstance.cancelLabel": "Keep running",
|
||||
|
||||
"emptyState.logoAlt": "CodeNomad logo",
|
||||
"emptyState.brandTitle": "CodeNomad",
|
||||
"emptyState.tagline": "Select a folder to start coding with AI",
|
||||
"emptyState.actions.selectFolder": "Select Folder",
|
||||
"emptyState.actions.selecting": "Selecting...",
|
||||
"emptyState.keyboardShortcut": "Keyboard shortcut: {shortcut}",
|
||||
"emptyState.examples": "Examples: {example}",
|
||||
"emptyState.multipleInstances": "You can have multiple instances of the same folder",
|
||||
|
||||
"releases.upgradeRequired.title": "Upgrade required",
|
||||
"releases.upgradeRequired.message.withVersion": "Update to CodeNomad {version} to use the latest UI.",
|
||||
"releases.upgradeRequired.message.noVersion": "Update CodeNomad to use the latest UI.",
|
||||
"releases.upgradeRequired.action.getUpdate": "Get update",
|
||||
} as const
|
||||
160
packages/ui/src/lib/i18n/messages/en/commands.ts
Normal file
160
packages/ui/src/lib/i18n/messages/en/commands.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
export const commandMessages = {
|
||||
"commandPalette.title": "Command Palette",
|
||||
"commandPalette.description": "Search and execute commands",
|
||||
"commandPalette.searchPlaceholder": "Type a command or search...",
|
||||
"commandPalette.empty": "No commands found for \"{query}\"",
|
||||
"commandPalette.category.customCommands": "Custom Commands",
|
||||
"commandPalette.category.instance": "Instance",
|
||||
"commandPalette.category.session": "Session",
|
||||
"commandPalette.category.agentModel": "Agent & Model",
|
||||
"commandPalette.category.inputFocus": "Input & Focus",
|
||||
"commandPalette.category.system": "System",
|
||||
"commandPalette.category.other": "Other",
|
||||
|
||||
"commands.newInstance.label": "New Instance",
|
||||
"commands.newInstance.description": "Open folder picker to create new instance",
|
||||
"commands.newInstance.keywords": "folder, project, workspace",
|
||||
|
||||
"commands.closeInstance.label": "Close Instance",
|
||||
"commands.closeInstance.description": "Stop current instance's server",
|
||||
"commands.closeInstance.keywords": "stop, quit, close",
|
||||
|
||||
"commands.nextInstance.label": "Next Instance",
|
||||
"commands.nextInstance.description": "Cycle to next instance tab",
|
||||
"commands.nextInstance.keywords": "switch, navigate",
|
||||
|
||||
"commands.previousInstance.label": "Previous Instance",
|
||||
"commands.previousInstance.description": "Cycle to previous instance tab",
|
||||
"commands.previousInstance.keywords": "switch, navigate",
|
||||
|
||||
"commands.newSession.label": "New Session",
|
||||
"commands.newSession.description": "Create a new parent session",
|
||||
"commands.newSession.keywords": "create, start",
|
||||
|
||||
"commands.closeSession.label": "Close Session",
|
||||
"commands.closeSession.description": "Close current parent session",
|
||||
"commands.closeSession.keywords": "close, stop",
|
||||
|
||||
"commands.scrubSessions.label": "Scrub Sessions",
|
||||
"commands.scrubSessions.description": "Remove empty sessions, subagent sessions that have completed their primary task, and extraneous forked sessions.",
|
||||
"commands.scrubSessions.keywords": "cleanup, blank, empty, sessions, remove, delete, scrub",
|
||||
|
||||
"commands.instanceInfo.label": "Instance Info",
|
||||
"commands.instanceInfo.description": "Open the instance overview for logs and status",
|
||||
"commands.instanceInfo.keywords": "info, logs, console, output",
|
||||
|
||||
"commands.nextSession.label": "Next Session",
|
||||
"commands.nextSession.description": "Cycle to next session tab",
|
||||
"commands.nextSession.keywords": "switch, navigate",
|
||||
|
||||
"commands.previousSession.label": "Previous Session",
|
||||
"commands.previousSession.description": "Cycle to previous session tab",
|
||||
"commands.previousSession.keywords": "switch, navigate",
|
||||
|
||||
"commands.compactSession.label": "Compact Session",
|
||||
"commands.compactSession.description": "Summarize and compact the current session",
|
||||
"commands.compactSession.keywords": "summarize, compress",
|
||||
"commands.compactSession.errorFallback": "Failed to compact session",
|
||||
"commands.compactSession.alert.title": "Compact failed",
|
||||
"commands.compactSession.alert.message": "Compact failed: {message}",
|
||||
|
||||
"commands.undoLastMessage.label": "Undo Last Message",
|
||||
"commands.undoLastMessage.description": "Revert the last message",
|
||||
"commands.undoLastMessage.keywords": "revert, undo",
|
||||
"commands.undoLastMessage.none.title": "No actions to undo",
|
||||
"commands.undoLastMessage.none.message": "Nothing to undo",
|
||||
"commands.undoLastMessage.failed.title": "Undo failed",
|
||||
"commands.undoLastMessage.failed.message": "Failed to revert message",
|
||||
|
||||
"commands.openModelSelector.label": "Open Model Selector",
|
||||
"commands.openModelSelector.description": "Choose a different model",
|
||||
"commands.openModelSelector.keywords": "model, llm, ai",
|
||||
|
||||
"commands.selectModelVariant.label": "Select Model Variant",
|
||||
"commands.selectModelVariant.description": "Choose a thinking effort for the current model",
|
||||
"commands.selectModelVariant.keywords": "variant, thinking, reasoning, effort",
|
||||
|
||||
"commands.openAgentSelector.label": "Open Agent Selector",
|
||||
"commands.openAgentSelector.description": "Choose a different agent",
|
||||
"commands.openAgentSelector.keywords": "agent, mode",
|
||||
|
||||
"commands.clearInput.label": "Clear Input",
|
||||
"commands.clearInput.description": "Clear the prompt textarea",
|
||||
"commands.clearInput.keywords": "clear, reset",
|
||||
|
||||
"commands.thinkingBlocks.label.show": "Show Thinking Blocks",
|
||||
"commands.thinkingBlocks.label.hide": "Hide Thinking Blocks",
|
||||
"commands.thinkingBlocks.description": "Show/hide AI thinking process",
|
||||
"commands.thinkingBlocks.keywords": "thinking, reasoning, toggle, show, hide",
|
||||
|
||||
"commands.timelineToolCalls.label.show": "Show Timeline Tool Calls",
|
||||
"commands.timelineToolCalls.label.hide": "Hide Timeline Tool Calls",
|
||||
"commands.timelineToolCalls.description": "Toggle tool call entries in the message timeline",
|
||||
"commands.timelineToolCalls.keywords": "timeline, tool, toggle",
|
||||
|
||||
"commands.common.expanded": "Expanded",
|
||||
"commands.common.collapsed": "Collapsed",
|
||||
"commands.common.visible": "Visible",
|
||||
"commands.common.hidden": "Hidden",
|
||||
"commands.common.enabled": "Enabled",
|
||||
"commands.common.disabled": "Disabled",
|
||||
|
||||
"commands.thinkingBlocksDefault.label": "Thinking Blocks Default · {state}",
|
||||
"commands.thinkingBlocksDefault.description": "Toggle whether thinking blocks start expanded",
|
||||
"commands.thinkingBlocksDefault.keywords": "thinking, reasoning, expand, collapse, default",
|
||||
|
||||
"commands.diffViewSplit.label": "Use Split Diff View",
|
||||
"commands.diffViewSplit.description": "Display tool-call diffs side-by-side",
|
||||
"commands.diffViewSplit.keywords": "diff, split, view",
|
||||
|
||||
"commands.diffViewUnified.label": "Use Unified Diff View",
|
||||
"commands.diffViewUnified.description": "Display tool-call diffs inline",
|
||||
"commands.diffViewUnified.keywords": "diff, unified, view",
|
||||
|
||||
"commands.toolOutputsDefault.label": "Tool Outputs Default · {state}",
|
||||
"commands.toolOutputsDefault.description": "Toggle default expansion for tool outputs",
|
||||
"commands.toolOutputsDefault.keywords": "tool, output, expand, collapse",
|
||||
|
||||
"commands.diagnosticsDefault.label": "Diagnostics Default · {state}",
|
||||
"commands.diagnosticsDefault.description": "Toggle default expansion for diagnostics output",
|
||||
"commands.diagnosticsDefault.keywords": "diagnostics, expand, collapse",
|
||||
|
||||
"commands.tokenUsageDisplay.label": "Token Usage Display · {state}",
|
||||
"commands.tokenUsageDisplay.description": "Show or hide token and cost stats for assistant messages",
|
||||
"commands.tokenUsageDisplay.keywords": "token, usage, cost, stats",
|
||||
|
||||
"commands.autoCleanupBlankSessions.label": "Auto-Cleanup Blank Sessions · {state}",
|
||||
"commands.autoCleanupBlankSessions.description": "Automatically clean up blank sessions when creating new ones",
|
||||
"commands.autoCleanupBlankSessions.keywords": "auto, cleanup, blank, sessions, toggle",
|
||||
|
||||
"commands.showHelp.label": "Show Help",
|
||||
"commands.showHelp.description": "Display keyboard shortcuts and help",
|
||||
"commands.showHelp.keywords": "shortcuts, help",
|
||||
|
||||
"commands.custom.argumentsPrompt.message": "Arguments for /{name}",
|
||||
"commands.custom.argumentsPrompt.title": "Custom command",
|
||||
"commands.custom.argumentsPrompt.inputLabel": "Arguments",
|
||||
"commands.custom.argumentsPrompt.inputPlaceholder": "e.g. foo bar",
|
||||
"commands.custom.argumentsPrompt.confirmLabel": "Run",
|
||||
"commands.custom.argumentsPrompt.cancelLabel": "Cancel",
|
||||
"commands.custom.argumentsPrompt.openFailed.message": "Failed to open arguments prompt.",
|
||||
"commands.custom.argumentsPrompt.openFailed.title": "Command arguments",
|
||||
"commands.custom.entries.descriptionFallback": "Custom command",
|
||||
"commands.custom.sessionRequired.message": "Select a session before running a custom command.",
|
||||
"commands.custom.sessionRequired.title": "Session required",
|
||||
"commands.custom.runFailed.message": "Failed to run custom command. Check the console for details.",
|
||||
"commands.custom.runFailed.title": "Command failed",
|
||||
|
||||
"unifiedPicker.loading.searching": "Searching...",
|
||||
"unifiedPicker.loading.loadingWorkspace": "Loading workspace...",
|
||||
"unifiedPicker.title.command": "Select Command",
|
||||
"unifiedPicker.title.mention": "Select Agent or File",
|
||||
"unifiedPicker.empty": "No results found",
|
||||
"unifiedPicker.sections.commands": "COMMANDS",
|
||||
"unifiedPicker.sections.agents": "AGENTS",
|
||||
"unifiedPicker.sections.files": "FILES",
|
||||
"unifiedPicker.badge.subagent": "subagent",
|
||||
"unifiedPicker.footer.navigate": "navigate",
|
||||
"unifiedPicker.footer.select": "select",
|
||||
"unifiedPicker.footer.close": "close",
|
||||
} as const
|
||||
16
packages/ui/src/lib/i18n/messages/en/dialogs.ts
Normal file
16
packages/ui/src/lib/i18n/messages/en/dialogs.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
export const dialogMessages = {
|
||||
"alertDialog.fallbackTitle.info": "Heads up",
|
||||
"alertDialog.fallbackTitle.warning": "Please review",
|
||||
"alertDialog.fallbackTitle.error": "Something went wrong",
|
||||
"alertDialog.actions.confirm": "Confirm",
|
||||
"alertDialog.actions.run": "Run",
|
||||
"alertDialog.actions.ok": "OK",
|
||||
"alertDialog.actions.cancel": "Cancel",
|
||||
"alertDialog.prompt.inputLabel": "Input",
|
||||
|
||||
"backgroundProcessOutputDialog.title": "Background Output",
|
||||
"backgroundProcessOutputDialog.actions.close": "Close",
|
||||
"backgroundProcessOutputDialog.loading": "Loading output...",
|
||||
"backgroundProcessOutputDialog.truncatedNotice": "Output truncated for display.",
|
||||
"backgroundProcessOutputDialog.loadErrorFallback": "Failed to load output.",
|
||||
} as const
|
||||
43
packages/ui/src/lib/i18n/messages/en/filesystem.ts
Normal file
43
packages/ui/src/lib/i18n/messages/en/filesystem.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
export const filesystemMessages = {
|
||||
"directoryBrowser.defaultDescription": "Browse folders under the configured workspace root.",
|
||||
"directoryBrowser.close": "Close",
|
||||
"directoryBrowser.currentFolder": "Current folder",
|
||||
"directoryBrowser.selectCurrent": "Select Current",
|
||||
"directoryBrowser.newFolder": "New Folder",
|
||||
"directoryBrowser.creating": "Creating…",
|
||||
"directoryBrowser.loadingFolders": "Loading folders…",
|
||||
"directoryBrowser.noFolders": "No folders available.",
|
||||
"directoryBrowser.upOneLevel": "Up one level",
|
||||
"directoryBrowser.select": "Select",
|
||||
"directoryBrowser.load.errorFallback": "Unable to load filesystem",
|
||||
"directoryBrowser.createFolder.promptMessage": "Create a new folder in the current directory.",
|
||||
"directoryBrowser.createFolder.title": "New Folder",
|
||||
"directoryBrowser.createFolder.inputLabel": "Folder name",
|
||||
"directoryBrowser.createFolder.inputPlaceholder": "e.g. my-new-project",
|
||||
"directoryBrowser.createFolder.confirmLabel": "Create",
|
||||
"directoryBrowser.createFolder.cancelLabel": "Cancel",
|
||||
"directoryBrowser.createFolder.invalidNameMessage": "Please enter a single folder name.",
|
||||
"directoryBrowser.createFolder.invalidNameDetail": "Folder names cannot include slashes, '..', or '~'.",
|
||||
"directoryBrowser.createFolder.errorFallback": "Unable to create folder",
|
||||
|
||||
"filesystemBrowser.descriptionFallback": "Search for a path under the configured workspace root.",
|
||||
"filesystemBrowser.rootLabel": "Root: {root}",
|
||||
"filesystemBrowser.actions.close": "Close",
|
||||
"filesystemBrowser.actions.retry": "Retry",
|
||||
"filesystemBrowser.actions.select": "Select",
|
||||
"filesystemBrowser.filterLabel": "Filter",
|
||||
"filesystemBrowser.search.placeholder.directories": "Search for folders",
|
||||
"filesystemBrowser.search.placeholder.files": "Search for files",
|
||||
"filesystemBrowser.currentFolder.label": "Current folder",
|
||||
"filesystemBrowser.currentFolder.selectCurrent": "Select Current",
|
||||
"filesystemBrowser.loading.filesystem": "filesystem",
|
||||
"filesystemBrowser.loading.workspaceRoot": "workspace root",
|
||||
"filesystemBrowser.loading.loadingWithPath": "Loading {path}…",
|
||||
"filesystemBrowser.empty.noEntries": "No entries found.",
|
||||
"filesystemBrowser.navigation.upOneLevel": "Up one level",
|
||||
"filesystemBrowser.hints.navigate": "Navigate",
|
||||
"filesystemBrowser.hints.select": "Select",
|
||||
"filesystemBrowser.hints.close": "Close",
|
||||
"filesystemBrowser.errors.loadFilesystemFallback": "Unable to load filesystem",
|
||||
"filesystemBrowser.errors.openDirectoryFallback": "Unable to open directory",
|
||||
} as const
|
||||
@@ -1,4 +1,4 @@
|
||||
export const enMessages = {
|
||||
export const folderSelectionMessages = {
|
||||
"folderSelection.logoAlt": "CodeNomad logo",
|
||||
"folderSelection.tagline": "Select a folder to start coding with AI",
|
||||
|
||||
@@ -31,9 +31,4 @@ export const enMessages = {
|
||||
|
||||
"folderSelection.dialog.title": "Select Workspace",
|
||||
"folderSelection.dialog.description": "Select workspace to start coding.",
|
||||
|
||||
"time.relative.justNow": "just now",
|
||||
"time.relative.daysAgoShort": "{count}d ago",
|
||||
"time.relative.hoursAgoShort": "{count}h ago",
|
||||
"time.relative.minutesAgoShort": "{count}m ago",
|
||||
} as const
|
||||
36
packages/ui/src/lib/i18n/messages/en/index.ts
Normal file
36
packages/ui/src/lib/i18n/messages/en/index.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { advancedSettingsMessages } from "./advancedSettings"
|
||||
import { appMessages } from "./app"
|
||||
import { commandMessages } from "./commands"
|
||||
import { dialogMessages } from "./dialogs"
|
||||
import { filesystemMessages } from "./filesystem"
|
||||
import { folderSelectionMessages } from "./folderSelection"
|
||||
import { instanceMessages } from "./instance"
|
||||
import { loadingScreenMessages } from "./loadingScreen"
|
||||
import { logMessages } from "./logs"
|
||||
import { markdownMessages } from "./markdown"
|
||||
import { messagingMessages } from "./messaging"
|
||||
import { remoteAccessMessages } from "./remoteAccess"
|
||||
import { sessionMessages } from "./session"
|
||||
import { settingsMessages } from "./settings"
|
||||
import { timeMessages } from "./time"
|
||||
import { toolCallMessages } from "./toolCall"
|
||||
import { mergeMessageParts } from "./merge"
|
||||
|
||||
export const enMessages = mergeMessageParts(
|
||||
folderSelectionMessages,
|
||||
advancedSettingsMessages,
|
||||
loadingScreenMessages,
|
||||
timeMessages,
|
||||
appMessages,
|
||||
dialogMessages,
|
||||
filesystemMessages,
|
||||
instanceMessages,
|
||||
logMessages,
|
||||
sessionMessages,
|
||||
messagingMessages,
|
||||
toolCallMessages,
|
||||
markdownMessages,
|
||||
settingsMessages,
|
||||
remoteAccessMessages,
|
||||
commandMessages,
|
||||
)
|
||||
125
packages/ui/src/lib/i18n/messages/en/instance.ts
Normal file
125
packages/ui/src/lib/i18n/messages/en/instance.ts
Normal file
@@ -0,0 +1,125 @@
|
||||
export const instanceMessages = {
|
||||
"instanceTabs.new.title": "New instance (Cmd/Ctrl+N)",
|
||||
"instanceTabs.new.ariaLabel": "New instance",
|
||||
"instanceTabs.remote.title": "Remote connect",
|
||||
"instanceTabs.remote.ariaLabel": "Remote connect",
|
||||
|
||||
"instanceInfo.title": "Instance Information",
|
||||
"instanceInfo.labels.folder": "Folder",
|
||||
"instanceInfo.labels.project": "Project",
|
||||
"instanceInfo.labels.versionControl": "Version Control",
|
||||
"instanceInfo.labels.opencodeVersion": "OpenCode Version",
|
||||
"instanceInfo.labels.binaryPath": "Binary Path",
|
||||
"instanceInfo.labels.environmentVariables": "Environment Variables ({count})",
|
||||
"instanceInfo.loading": "Loading...",
|
||||
"instanceInfo.server.title": "Server",
|
||||
"instanceInfo.server.port": "Port:",
|
||||
"instanceInfo.server.pid": "PID:",
|
||||
"instanceInfo.server.status": "Status:",
|
||||
|
||||
"instanceTab.status.permission": "Waiting on permission",
|
||||
"instanceTab.status.compacting": "Compacting",
|
||||
"instanceTab.status.working": "Working",
|
||||
"instanceTab.status.idle": "Idle",
|
||||
"instanceTab.status.ariaLabel": "Instance status: {status}",
|
||||
"instanceTab.actions.close.ariaLabel": "Close instance",
|
||||
|
||||
"instanceShell.leftPanel.sessionsTitle": "Sessions",
|
||||
"instanceShell.leftPanel.instanceInfo": "Instance Info",
|
||||
|
||||
"instanceShell.leftDrawer.pin": "Pin left drawer",
|
||||
"instanceShell.leftDrawer.unpin": "Unpin left drawer",
|
||||
"instanceShell.leftDrawer.toggle.pinned": "Left drawer pinned",
|
||||
"instanceShell.leftDrawer.toggle.open": "Open left drawer",
|
||||
"instanceShell.leftDrawer.toggle.close": "Close left drawer",
|
||||
|
||||
"instanceShell.rightDrawer.pin": "Pin right drawer",
|
||||
"instanceShell.rightDrawer.unpin": "Unpin right drawer",
|
||||
"instanceShell.rightDrawer.toggle.pinned": "Right drawer pinned",
|
||||
"instanceShell.rightDrawer.toggle.open": "Open right drawer",
|
||||
"instanceShell.rightDrawer.toggle.close": "Close right drawer",
|
||||
|
||||
"instanceShell.metrics.usedLabel": "Used",
|
||||
"instanceShell.metrics.availableLabel": "Avail",
|
||||
|
||||
"instanceShell.commandPalette.openAriaLabel": "Open command palette",
|
||||
"instanceShell.commandPalette.button": "Command Palette",
|
||||
|
||||
"instanceShell.connection.ariaLabel": "Connection {status}",
|
||||
"instanceShell.connection.connected": "Connected",
|
||||
"instanceShell.connection.connecting": "Connecting...",
|
||||
"instanceShell.connection.disconnected": "Disconnected",
|
||||
"instanceShell.connection.unknown": "Unknown",
|
||||
|
||||
"instanceWelcome.shortcuts.newSession": "New Session",
|
||||
"instanceWelcome.empty.title": "No Previous Sessions",
|
||||
"instanceWelcome.empty.description": "Create a new session below to get started",
|
||||
"instanceWelcome.loading.title": "Loading Sessions",
|
||||
"instanceWelcome.loading.description": "Fetching your previous sessions...",
|
||||
"instanceWelcome.resume.title": "Resume Session",
|
||||
"instanceWelcome.resume.subtitle.one": "{count} session available",
|
||||
"instanceWelcome.resume.subtitle.other": "{count} sessions available",
|
||||
"instanceWelcome.session.untitled": "Untitled Session",
|
||||
"instanceWelcome.new.title": "Start New Session",
|
||||
"instanceWelcome.new.subtitle": "We’ll reuse your last agent/model automatically",
|
||||
"instanceWelcome.new.createButton": "Create Session",
|
||||
"instanceWelcome.overlay.close": "Close",
|
||||
"instanceWelcome.actions.viewInstanceInfo": "View Instance Info",
|
||||
"instanceWelcome.actions.renameTitle": "Rename session",
|
||||
"instanceWelcome.actions.deleteTitle": "Delete session",
|
||||
"instanceWelcome.hints.navigate": "Navigate",
|
||||
"instanceWelcome.hints.jump": "Jump",
|
||||
"instanceWelcome.hints.firstLast": "First/Last",
|
||||
"instanceWelcome.hints.resume": "Resume",
|
||||
"instanceWelcome.hints.delete": "Delete",
|
||||
"instanceWelcome.toasts.renameError": "Unable to rename session",
|
||||
|
||||
"instanceDisconnected.title": "Instance Disconnected",
|
||||
"instanceDisconnected.folderFallback": "this workspace",
|
||||
"instanceDisconnected.reasonFallback": "The server stopped responding",
|
||||
"instanceDisconnected.description": "{folder} can no longer be reached. Close the tab to continue working.",
|
||||
"instanceDisconnected.details.title": "Details",
|
||||
"instanceDisconnected.details.folderLabel": "Folder:",
|
||||
"instanceDisconnected.actions.closeInstance": "Close Instance",
|
||||
|
||||
"instanceShell.empty.title": "No session selected",
|
||||
"instanceShell.empty.description": "Select a session to view messages",
|
||||
|
||||
"instanceShell.rightPanel.title": "Status Panel",
|
||||
"instanceShell.rightPanel.sections.plan": "Plan",
|
||||
"instanceShell.rightPanel.sections.backgroundProcesses": "Background Shells",
|
||||
"instanceShell.rightPanel.sections.mcp": "MCP Servers",
|
||||
"instanceShell.rightPanel.sections.lsp": "LSP Servers",
|
||||
"instanceShell.rightPanel.sections.plugins": "Plugins",
|
||||
|
||||
"instanceShell.plan.noSessionSelected": "Select a session to view plan.",
|
||||
"instanceShell.plan.empty": "Nothing planned yet.",
|
||||
|
||||
"instanceShell.backgroundProcesses.empty": "No background processes.",
|
||||
"instanceShell.backgroundProcesses.status": "Status: {status}",
|
||||
"instanceShell.backgroundProcesses.output": "Output: {sizeKb}KB",
|
||||
"instanceShell.backgroundProcesses.actions.output": "Output",
|
||||
"instanceShell.backgroundProcesses.actions.stop": "Stop",
|
||||
"instanceShell.backgroundProcesses.actions.terminate": "Terminate",
|
||||
|
||||
"versionPill.appWithVersion": "App {version}",
|
||||
"versionPill.ui": "UI",
|
||||
"versionPill.uiWithVersion": "UI {version}",
|
||||
"versionPill.source": " ({source})",
|
||||
|
||||
"opencodeBinarySelector.title": "OpenCode Binary",
|
||||
"opencodeBinarySelector.subtitle": "Choose which executable OpenCode should run",
|
||||
"opencodeBinarySelector.customPath.placeholder": "Enter path to opencode binary…",
|
||||
"opencodeBinarySelector.actions.add": "Add",
|
||||
"opencodeBinarySelector.actions.browse": "Browse for Binary…",
|
||||
"opencodeBinarySelector.actions.removeTitle": "Remove binary",
|
||||
"opencodeBinarySelector.badge.systemPath": "Use binary from system PATH",
|
||||
"opencodeBinarySelector.status.checkingVersions": "Checking versions…",
|
||||
"opencodeBinarySelector.status.checking": "Checking…",
|
||||
"opencodeBinarySelector.dialog.title": "Select OpenCode Binary",
|
||||
"opencodeBinarySelector.dialog.description": "Browse files exposed by the CLI server.",
|
||||
"opencodeBinarySelector.validation.invalidBinary": "Invalid OpenCode binary",
|
||||
"opencodeBinarySelector.validation.alreadyValidating": "Already validating",
|
||||
"opencodeBinarySelector.display.systemPath": "{name} (system PATH)",
|
||||
"opencodeBinarySelector.versionLabel": "v{version}",
|
||||
} as const
|
||||
17
packages/ui/src/lib/i18n/messages/en/loadingScreen.ts
Normal file
17
packages/ui/src/lib/i18n/messages/en/loadingScreen.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
export const loadingScreenMessages = {
|
||||
"loadingScreen.logoAlt": "CodeNomad logo",
|
||||
"loadingScreen.status.issue": "Encountered an issue",
|
||||
"loadingScreen.actions.showAnother": "Show another",
|
||||
"loadingScreen.errors.missingRoot": "Loading root element not found",
|
||||
|
||||
"loadingScreen.phrases.neurons": "Warming up the AI neurons…",
|
||||
"loadingScreen.phrases.daydreaming": "Convincing the AI to stop daydreaming…",
|
||||
"loadingScreen.phrases.goggles": "Polishing the AI’s code goggles…",
|
||||
"loadingScreen.phrases.reorganizingFiles": "Asking the AI to stop reorganizing your files…",
|
||||
"loadingScreen.phrases.coffee": "Feeding the AI additional coffee…",
|
||||
"loadingScreen.phrases.nodeModules": "Teaching the AI not to delete node_modules (again)…",
|
||||
"loadingScreen.phrases.actNatural": "Telling the AI to act natural before you arrive…",
|
||||
"loadingScreen.phrases.rewritingHistory": "Asking the AI to please stop rewriting history…",
|
||||
"loadingScreen.phrases.stretch": "Letting the AI stretch before its coding sprint…",
|
||||
"loadingScreen.phrases.keyboardControl": "Persuading the AI to give you keyboard control…",
|
||||
} as const
|
||||
18
packages/ui/src/lib/i18n/messages/en/logs.ts
Normal file
18
packages/ui/src/lib/i18n/messages/en/logs.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export const logMessages = {
|
||||
"logsView.title": "Server Logs",
|
||||
"logsView.actions.show": "Show server logs",
|
||||
"logsView.actions.hide": "Hide server logs",
|
||||
"logsView.envVars.title": "Environment Variables ({count})",
|
||||
"logsView.paused.title": "Server logs are paused",
|
||||
"logsView.paused.description": "Enable streaming to watch your OpenCode server activity.",
|
||||
"logsView.empty.waiting": "Waiting for server output...",
|
||||
"logsView.scrollToBottom": "Scroll to bottom",
|
||||
|
||||
"infoView.logs.title": "Server Logs",
|
||||
"infoView.logs.actions.show": "Show server logs",
|
||||
"infoView.logs.actions.hide": "Hide server logs",
|
||||
"infoView.logs.paused.title": "Server logs are paused",
|
||||
"infoView.logs.paused.description": "Enable streaming to watch your OpenCode server activity.",
|
||||
"infoView.logs.empty.waiting": "Waiting for server output...",
|
||||
"infoView.logs.scrollToBottom": "Scroll to bottom",
|
||||
} as const
|
||||
7
packages/ui/src/lib/i18n/messages/en/markdown.ts
Normal file
7
packages/ui/src/lib/i18n/messages/en/markdown.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
export const markdownMessages = {
|
||||
"markdown.codeBlock.copy.label": "Copy",
|
||||
"markdown.codeBlock.copy.copied": "Copied!",
|
||||
"markdown.codeBlock.copy.failed": "Failed",
|
||||
|
||||
"markdown.copy": "Copy",
|
||||
} as const
|
||||
25
packages/ui/src/lib/i18n/messages/en/merge.ts
Normal file
25
packages/ui/src/lib/i18n/messages/en/merge.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
export type MessageCatalog = Record<string, string>
|
||||
|
||||
type MergeParts<Parts extends readonly MessageCatalog[]> = Parts extends readonly [
|
||||
infer Head extends MessageCatalog,
|
||||
...infer Tail extends MessageCatalog[],
|
||||
]
|
||||
? Head & MergeParts<Tail>
|
||||
: {}
|
||||
|
||||
export function mergeMessageParts<const Parts extends readonly MessageCatalog[]>(
|
||||
...parts: Parts
|
||||
): MergeParts<Parts> {
|
||||
const result: Record<string, string> = Object.create(null)
|
||||
|
||||
for (const part of parts) {
|
||||
for (const [key, value] of Object.entries(part)) {
|
||||
if (key in result) {
|
||||
throw new Error(`Duplicate i18n message key: ${key}`)
|
||||
}
|
||||
result[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return result as MergeParts<Parts>
|
||||
}
|
||||
109
packages/ui/src/lib/i18n/messages/en/messaging.ts
Normal file
109
packages/ui/src/lib/i18n/messages/en/messaging.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
export const messagingMessages = {
|
||||
"messageListHeader.sidebar.openSessionListAriaLabel": "Open session list",
|
||||
"messageListHeader.metrics.usedLabel": "Used",
|
||||
"messageListHeader.metrics.availableLabel": "Avail",
|
||||
"messageListHeader.commandPalette.ariaLabel": "Open command palette",
|
||||
"messageListHeader.commandPalette.button": "Command Palette",
|
||||
"messageListHeader.connection.connected": "Connected",
|
||||
"messageListHeader.connection.connecting": "Connecting...",
|
||||
"messageListHeader.connection.disconnected": "Disconnected",
|
||||
|
||||
"messageSection.empty.logoAlt": "CodeNomad logo",
|
||||
"messageSection.empty.brandTitle": "CodeNomad",
|
||||
"messageSection.empty.title": "Start a conversation",
|
||||
"messageSection.empty.description": "Type a message below or open the Command Palette:",
|
||||
"messageSection.empty.tips.commandPalette": "Command Palette",
|
||||
"messageSection.empty.tips.askAboutCodebase": "Ask about your codebase",
|
||||
"messageSection.empty.tips.attachFilesPrefix": "Attach files with",
|
||||
"messageSection.loading.messages": "Loading messages...",
|
||||
"messageSection.scroll.toFirstAriaLabel": "Scroll to first message",
|
||||
"messageSection.scroll.toLatestAriaLabel": "Scroll to latest message",
|
||||
"messageSection.quote.addAsQuote": "Add as quote",
|
||||
"messageSection.quote.addAsCode": "Add as code",
|
||||
|
||||
"messageTimeline.ariaLabel": "Message timeline",
|
||||
"messageTimeline.segment.user.label": "You",
|
||||
"messageTimeline.segment.assistant.label": "Asst",
|
||||
"messageTimeline.segment.compaction.label": "Compaction",
|
||||
"messageTimeline.tool.fallbackLabel": "Tool Call",
|
||||
"messageTimeline.tooltip.userFallback": "User message",
|
||||
"messageTimeline.tooltip.assistantFallback": "Assistant response",
|
||||
"messageTimeline.tooltip.compaction.auto": "Auto Compaction",
|
||||
"messageTimeline.tooltip.compaction.manual": "User Compaction",
|
||||
"messageTimeline.text.filePrefix": "[File] {filename}",
|
||||
"messageTimeline.text.attachment": "Attachment",
|
||||
|
||||
"messageBlock.tool.header": "Tool Call",
|
||||
"messageBlock.tool.unknown": "unknown",
|
||||
"messageBlock.tool.goToSession.label": "Go to Session",
|
||||
"messageBlock.tool.goToSession.title": "Go to session",
|
||||
"messageBlock.tool.goToSession.unavailableTitle": "Session not available yet",
|
||||
|
||||
"messageBlock.compaction.ariaLabel": "Session compaction",
|
||||
"messageBlock.compaction.autoLabel": "Session auto-compacted",
|
||||
"messageBlock.compaction.manualLabel": "Session compacted by you",
|
||||
"messageBlock.usage.input": "Input",
|
||||
"messageBlock.usage.output": "Output",
|
||||
"messageBlock.usage.reasoning": "Reasoning",
|
||||
"messageBlock.usage.cacheRead": "Cache Read",
|
||||
"messageBlock.usage.cacheWrite": "Cache Write",
|
||||
"messageBlock.usage.cost": "Cost",
|
||||
"messageBlock.step.agentLabel": "Agent: {agent}",
|
||||
"messageBlock.step.modelLabel": "Model: {model}",
|
||||
"messageBlock.reasoning.thinkingLabel": "Thinking",
|
||||
"messageBlock.reasoning.expandAriaLabel": "Expand thinking",
|
||||
"messageBlock.reasoning.collapseAriaLabel": "Collapse thinking",
|
||||
"messageBlock.reasoning.indicator.hide": "Hide",
|
||||
"messageBlock.reasoning.indicator.view": "View",
|
||||
"messageBlock.reasoning.detailsAriaLabel": "Reasoning details",
|
||||
|
||||
"codeBlockInline.actions.copy": "Copy",
|
||||
"codeBlockInline.actions.copied": "Copied!",
|
||||
|
||||
"messageItem.speaker.you": "You",
|
||||
"messageItem.speaker.assistant": "Assistant",
|
||||
"messageItem.actions.revert": "Revert",
|
||||
"messageItem.actions.revertTitle": "Revert to this message",
|
||||
"messageItem.actions.fork": "Fork",
|
||||
"messageItem.actions.forkTitle": "Fork from this message",
|
||||
"messageItem.actions.copy": "Copy",
|
||||
"messageItem.actions.copyTitle": "Copy message",
|
||||
"messageItem.actions.copied": "Copied!",
|
||||
"messageItem.status.queued": "QUEUED",
|
||||
"messageItem.status.generating": "Generating...",
|
||||
"messageItem.status.sending": "Sending...",
|
||||
"messageItem.status.failedToSend": "Message failed to send",
|
||||
"messageItem.attachment.defaultName": "attachment",
|
||||
"messageItem.attachment.downloadAriaLabel": "Download {name}",
|
||||
"messageItem.agentMeta.agentLabel": "Agent: {agent}",
|
||||
"messageItem.agentMeta.modelLabel": "Model: {model}",
|
||||
"messageItem.errors.authenticationFallback": "Authentication error",
|
||||
"messageItem.errors.outputLengthExceeded": "Message output length exceeded",
|
||||
"messageItem.errors.requestAborted": "Request was aborted",
|
||||
"messageItem.errors.unknownFallback": "Unknown error occurred",
|
||||
|
||||
"attachmentChip.removeAriaLabel": "Remove attachment",
|
||||
|
||||
"expandButton.toggleAriaLabel": "Toggle chat input height",
|
||||
|
||||
"promptInput.placeholder.shell": "Run a shell command (Esc to exit)...",
|
||||
"promptInput.placeholder.default": "Type your message, @file, @agent, or paste images and text...",
|
||||
"promptInput.hints.shell.exit": "to exit shell mode",
|
||||
"promptInput.hints.shell.enable": "Shell mode",
|
||||
"promptInput.hints.commands": "Commands",
|
||||
"promptInput.history.previousAriaLabel": "Previous prompt",
|
||||
"promptInput.history.nextAriaLabel": "Next prompt",
|
||||
"promptInput.overlay.newLine": "New line",
|
||||
"promptInput.overlay.send": "Send",
|
||||
"promptInput.overlay.filesAgents": "Files/agents",
|
||||
"promptInput.overlay.history": "History",
|
||||
"promptInput.overlay.attachments": "• {count} file(s) attached",
|
||||
"promptInput.overlay.shellModeActive": "Shell mode active",
|
||||
"promptInput.overlay.press": "Press",
|
||||
"promptInput.overlay.againToAbort": "again to abort session",
|
||||
"promptInput.stopSession.ariaLabel": "Stop session",
|
||||
"promptInput.stopSession.title": "Stop session",
|
||||
"promptInput.send.ariaLabel": "Send message",
|
||||
"promptInput.send.errorFallback": "Failed to send message",
|
||||
"promptInput.send.errorTitle": "Send failed",
|
||||
} as const
|
||||
51
packages/ui/src/lib/i18n/messages/en/remoteAccess.ts
Normal file
51
packages/ui/src/lib/i18n/messages/en/remoteAccess.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
export const remoteAccessMessages = {
|
||||
"remoteAccess.eyebrow": "Remote handover",
|
||||
"remoteAccess.title": "Connect to CodeNomad remotely",
|
||||
"remoteAccess.subtitle": "Use the addresses below to open CodeNomad from another device.",
|
||||
"remoteAccess.close": "Close remote access",
|
||||
"remoteAccess.refresh": "Refresh",
|
||||
|
||||
"remoteAccess.sections.listeningMode.label": "Listening mode",
|
||||
"remoteAccess.sections.listeningMode.help": "Allow or limit remote handovers by binding to all interfaces or just localhost.",
|
||||
"remoteAccess.toggle.on": "On",
|
||||
"remoteAccess.toggle.off": "Off",
|
||||
"remoteAccess.toggle.title": "Allow connections from other IPs",
|
||||
"remoteAccess.toggle.caption.all": "Binding to 0.0.0.0",
|
||||
"remoteAccess.toggle.caption.local": "Binding to 127.0.0.1",
|
||||
"remoteAccess.toggle.note": "Changing this requires a restart and temporarily stops all active instances. Share the addresses below once the server restarts.",
|
||||
"remoteAccess.listeningMode.restartConfirm.message": "Restart to apply listening mode? This will stop all running instances.",
|
||||
"remoteAccess.listeningMode.restartConfirm.title.all": "Open to other devices",
|
||||
"remoteAccess.listeningMode.restartConfirm.title.local": "Limit to this device",
|
||||
"remoteAccess.listeningMode.restartConfirm.confirmLabel": "Restart now",
|
||||
"remoteAccess.listeningMode.restartConfirm.cancelLabel": "Cancel",
|
||||
"remoteAccess.restart.errorManual": "Unable to restart automatically. Please restart the app to apply the change.",
|
||||
|
||||
"remoteAccess.sections.serverPassword.label": "Server password",
|
||||
"remoteAccess.sections.serverPassword.help": "Remote handovers require a password. Set a memorable one to enable logins from other devices.",
|
||||
"remoteAccess.authStatus.unavailable": "Authentication status unavailable.",
|
||||
"remoteAccess.username": "Username: {username}",
|
||||
"remoteAccess.password.status.set": "A password is set for remote access.",
|
||||
"remoteAccess.password.status.unset": "No memorable password is set yet. Set one to allow remote handover logins.",
|
||||
"remoteAccess.password.actions.cancel": "Cancel",
|
||||
"remoteAccess.password.actions.change": "Change password",
|
||||
"remoteAccess.password.actions.set": "Set password",
|
||||
"remoteAccess.password.form.newPassword": "New password",
|
||||
"remoteAccess.password.form.confirmPassword": "Confirm password",
|
||||
"remoteAccess.password.form.placeholder": "At least 8 characters",
|
||||
"remoteAccess.password.error.tooShort": "Password must be at least 8 characters.",
|
||||
"remoteAccess.password.error.mismatch": "Passwords do not match.",
|
||||
"remoteAccess.password.save.saving": "Saving…",
|
||||
"remoteAccess.password.save.label": "Save password",
|
||||
|
||||
"remoteAccess.sections.addresses.label": "Reachable addresses",
|
||||
"remoteAccess.sections.addresses.help": "Launch or scan from another machine to hand over control.",
|
||||
"remoteAccess.addresses.loading": "Loading addresses…",
|
||||
"remoteAccess.addresses.none": "No addresses available yet.",
|
||||
"remoteAccess.address.scope.network": "Network",
|
||||
"remoteAccess.address.scope.loopback": "Loopback",
|
||||
"remoteAccess.address.scope.internal": "Internal",
|
||||
"remoteAccess.address.open": "Open",
|
||||
"remoteAccess.address.showQr": "Show QR",
|
||||
"remoteAccess.address.hideQr": "Hide QR",
|
||||
"remoteAccess.address.qrAlt": "QR for {url}",
|
||||
} as const
|
||||
67
packages/ui/src/lib/i18n/messages/en/session.ts
Normal file
67
packages/ui/src/lib/i18n/messages/en/session.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
export const sessionMessages = {
|
||||
"sessionPicker.title": "OpenCode • {folder}",
|
||||
"sessionPicker.empty.noPrevious": "No previous sessions",
|
||||
"sessionPicker.resume.title": "Resume a session ({count}):",
|
||||
"sessionPicker.session.untitled": "Untitled",
|
||||
"sessionPicker.divider.or": "or",
|
||||
"sessionPicker.new.title": "Start new session:",
|
||||
"sessionPicker.agents.loading": "Loading agents...",
|
||||
"sessionPicker.actions.creating": "Creating...",
|
||||
"sessionPicker.actions.createSession": "Create Session",
|
||||
"sessionPicker.actions.cancel": "Cancel",
|
||||
|
||||
"sessionList.header.title": "Sessions",
|
||||
"sessionList.session.untitled": "Untitled",
|
||||
"sessionList.status.working": "Working",
|
||||
"sessionList.status.compacting": "Compacting",
|
||||
"sessionList.status.idle": "Idle",
|
||||
"sessionList.status.needsPermission": "Needs Permission",
|
||||
"sessionList.status.needsInput": "Needs Input",
|
||||
"sessionList.expand.collapseAriaLabel": "Collapse session",
|
||||
"sessionList.expand.expandAriaLabel": "Expand session",
|
||||
"sessionList.expand.collapseTitle": "Collapse",
|
||||
"sessionList.expand.expandTitle": "Expand",
|
||||
"sessionList.actions.copyId.ariaLabel": "Copy session ID",
|
||||
"sessionList.actions.copyId.title": "Copy session ID",
|
||||
"sessionList.actions.rename.ariaLabel": "Rename session",
|
||||
"sessionList.actions.rename.title": "Rename session",
|
||||
"sessionList.actions.delete.ariaLabel": "Delete session",
|
||||
"sessionList.actions.delete.title": "Delete session",
|
||||
"sessionList.copyId.success": "Session ID copied",
|
||||
"sessionList.copyId.error": "Unable to copy session ID",
|
||||
"sessionList.delete.error": "Unable to delete session",
|
||||
"sessionList.rename.error": "Unable to rename session",
|
||||
|
||||
"sessionRenameDialog.title": "Rename Session",
|
||||
"sessionRenameDialog.description.withLabel": "Update the title for \"{label}\".",
|
||||
"sessionRenameDialog.description.default": "Set a new title for this session.",
|
||||
"sessionRenameDialog.input.label": "Session name",
|
||||
"sessionRenameDialog.input.placeholder": "Enter a session name",
|
||||
"sessionRenameDialog.actions.cancel": "Cancel",
|
||||
"sessionRenameDialog.actions.rename": "Rename",
|
||||
"sessionRenameDialog.actions.renaming": "Renaming…",
|
||||
|
||||
"sessionView.fallback.sessionNotFound": "Session not found",
|
||||
"sessionView.alerts.abortFailed.message": "Failed to stop session",
|
||||
"sessionView.alerts.abortFailed.title": "Stop failed",
|
||||
"sessionView.alerts.revertFailed.message": "Failed to revert to message",
|
||||
"sessionView.alerts.revertFailed.title": "Revert failed",
|
||||
"sessionView.alerts.forkFailed.message": "Failed to fork session",
|
||||
"sessionView.alerts.forkFailed.title": "Fork failed",
|
||||
"sessionView.attachments.expandPastedTextAriaLabel": "Expand pasted text",
|
||||
"sessionView.attachments.insertPastedTextTitle": "Insert pasted text",
|
||||
"sessionView.attachments.removeAriaLabel": "Remove attachment",
|
||||
|
||||
"sessionEvents.sessionCompactedToast": "Session {label} was compacted",
|
||||
"sessionEvents.sessionError.unknown": "Unknown error",
|
||||
"sessionEvents.sessionError.title": "Session error",
|
||||
"sessionEvents.sessionError.message": "Error: {message}",
|
||||
|
||||
"sessionState.cleanup.deepConfirm.message": "This cleanup may be slow, and may delete sessions you didn't intend to delete. Are you sure?",
|
||||
"sessionState.cleanup.deepConfirm.title": "Deep Clean Sessions",
|
||||
"sessionState.cleanup.deepConfirm.detail": "Deep Clean Sessions will delete all sessions that have no messages, remove any finished sub-agent sessions, and clear out any unused forks of a session.",
|
||||
"sessionState.cleanup.deepConfirm.confirmLabel": "Continue",
|
||||
"sessionState.cleanup.deepConfirm.cancelLabel": "Cancel",
|
||||
"sessionState.cleanup.toast.one": "Cleaned up {count} blank session",
|
||||
"sessionState.cleanup.toast.other": "Cleaned up {count} blank sessions",
|
||||
} as const
|
||||
54
packages/ui/src/lib/i18n/messages/en/settings.ts
Normal file
54
packages/ui/src/lib/i18n/messages/en/settings.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
export const settingsMessages = {
|
||||
"instanceServiceStatus.sections.lsp": "LSP Servers",
|
||||
"instanceServiceStatus.sections.mcp": "MCP Servers",
|
||||
"instanceServiceStatus.sections.plugins": "Plugins",
|
||||
"instanceServiceStatus.lsp.loading": "Loading LSP servers...",
|
||||
"instanceServiceStatus.lsp.empty": "No LSP servers detected.",
|
||||
"instanceServiceStatus.lsp.status.connected": "Connected",
|
||||
"instanceServiceStatus.lsp.status.error": "Error",
|
||||
"instanceServiceStatus.mcp.loading": "Loading MCP servers...",
|
||||
"instanceServiceStatus.mcp.empty": "No MCP servers detected.",
|
||||
"instanceServiceStatus.mcp.toggleAriaLabel": "Toggle {name} MCP server",
|
||||
"instanceServiceStatus.plugins.loading": "Loading plugins...",
|
||||
"instanceServiceStatus.plugins.empty": "No plugins configured.",
|
||||
|
||||
"permissionBanner.pendingRequests.one": "{count} pending request",
|
||||
"permissionBanner.pendingRequests.other": "{count} pending requests",
|
||||
"permissionBanner.detail.permission.one": "{count} permission",
|
||||
"permissionBanner.detail.permission.other": "{count} permissions",
|
||||
"permissionBanner.detail.question.one": "{count} question",
|
||||
"permissionBanner.detail.question.other": "{count} questions",
|
||||
"permissionBanner.detail.wrapper": " ({detail})",
|
||||
|
||||
"agentSelector.placeholder": "Select agent...",
|
||||
"agentSelector.badge.subagent": "subagent",
|
||||
"agentSelector.none": "None",
|
||||
"agentSelector.trigger.primary": "Agent: {agent}",
|
||||
|
||||
"modelSelector.placeholder.search": "Search models...",
|
||||
"modelSelector.none": "None",
|
||||
"modelSelector.trigger.primary": "Model: {model}",
|
||||
|
||||
"thinkingSelector.variant.default": "Default",
|
||||
"thinkingSelector.label": "Thinking: {variant}",
|
||||
|
||||
"envEditor.title": "Environment Variables",
|
||||
"envEditor.count.one": "({count} variable)",
|
||||
"envEditor.count.other": "({count} variables)",
|
||||
"envEditor.fields.name.placeholder": "Variable name",
|
||||
"envEditor.fields.name.readOnlyTitle": "Variable name (read-only)",
|
||||
"envEditor.fields.value.placeholder": "Variable value",
|
||||
"envEditor.actions.remove.title": "Remove variable",
|
||||
"envEditor.actions.add.title": "Add variable",
|
||||
"envEditor.empty": "No environment variables configured. Add variables above to customize the OpenCode environment.",
|
||||
"envEditor.help": "These variables will be available in the OpenCode environment when starting instances.",
|
||||
|
||||
"contextUsagePanel.headings.tokens": "Tokens",
|
||||
"contextUsagePanel.headings.context": "Context",
|
||||
"contextUsagePanel.labels.input": "Input",
|
||||
"contextUsagePanel.labels.output": "Output",
|
||||
"contextUsagePanel.labels.cost": "Cost",
|
||||
"contextUsagePanel.labels.used": "Used",
|
||||
"contextUsagePanel.labels.available": "Avail",
|
||||
"contextUsagePanel.unavailable": "--",
|
||||
} as const
|
||||
6
packages/ui/src/lib/i18n/messages/en/time.ts
Normal file
6
packages/ui/src/lib/i18n/messages/en/time.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const timeMessages = {
|
||||
"time.relative.justNow": "just now",
|
||||
"time.relative.daysAgoShort": "{count}d ago",
|
||||
"time.relative.hoursAgoShort": "{count}h ago",
|
||||
"time.relative.minutesAgoShort": "{count}m ago",
|
||||
} as const
|
||||
121
packages/ui/src/lib/i18n/messages/en/toolCall.ts
Normal file
121
packages/ui/src/lib/i18n/messages/en/toolCall.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
export const toolCallMessages = {
|
||||
"toolCall.pending.waitingToRun": "Waiting to run...",
|
||||
"toolCall.error.label": "Error:",
|
||||
|
||||
"toolCall.diff.label": "Diff",
|
||||
"toolCall.diff.label.withPath": "Diff · {path}",
|
||||
"toolCall.diff.viewMode.ariaLabel": "Diff view mode",
|
||||
"toolCall.diff.viewMode.split": "Split",
|
||||
"toolCall.diff.viewMode.unified": "Unified",
|
||||
|
||||
"toolCall.diagnostics.title": "Diagnostics",
|
||||
"toolCall.diagnostics.ariaLabel": "Diagnostics",
|
||||
"toolCall.diagnostics.ariaLabel.withLabel": "Diagnostics {label}",
|
||||
"toolCall.diagnostics.severity.error.short": "ERR",
|
||||
"toolCall.diagnostics.severity.warning.short": "WARN",
|
||||
"toolCall.diagnostics.severity.info.short": "INFO",
|
||||
|
||||
"toolCall.renderer.toolName.shell": "Shell",
|
||||
"toolCall.renderer.toolName.fetch": "Fetch",
|
||||
"toolCall.renderer.toolName.invalid": "Invalid",
|
||||
"toolCall.renderer.toolName.plan": "Plan",
|
||||
"toolCall.renderer.toolName.applyPatch": "Apply patch",
|
||||
|
||||
"toolCall.renderer.action.working": "Working...",
|
||||
"toolCall.renderer.action.writingCommand": "Writing command...",
|
||||
"toolCall.renderer.action.preparingEdit": "Preparing edit...",
|
||||
"toolCall.renderer.action.readingFile": "Reading file...",
|
||||
"toolCall.renderer.action.preparingWrite": "Preparing write...",
|
||||
"toolCall.renderer.action.preparingPatch": "Preparing patch...",
|
||||
"toolCall.renderer.action.planning": "Planning...",
|
||||
"toolCall.renderer.action.fetchingFromWeb": "Fetching from the web...",
|
||||
"toolCall.renderer.action.findingFiles": "Finding files...",
|
||||
"toolCall.renderer.action.searchingContent": "Searching content...",
|
||||
"toolCall.renderer.action.listingDirectory": "Listing directory...",
|
||||
|
||||
"toolCall.renderer.bash.title.timeout": "Timeout: {timeout}",
|
||||
"toolCall.renderer.read.detail.offset": "Offset: {offset}",
|
||||
"toolCall.renderer.read.detail.limit": "Limit: {limit}",
|
||||
|
||||
"toolCall.renderer.todo.empty": "No plan items yet.",
|
||||
"toolCall.renderer.todo.status.pending": "Pending",
|
||||
"toolCall.renderer.todo.status.inProgress": "In progress",
|
||||
"toolCall.renderer.todo.status.completed": "Completed",
|
||||
"toolCall.renderer.todo.status.cancelled": "Cancelled",
|
||||
"toolCall.renderer.todo.title.plan": "Plan",
|
||||
"toolCall.renderer.todo.title.creating": "Creating plan",
|
||||
"toolCall.renderer.todo.title.completing": "Completing plan",
|
||||
"toolCall.renderer.todo.title.updating": "Updating plan",
|
||||
|
||||
"toolCall.permission.status.required": "Permission Required",
|
||||
"toolCall.permission.status.queued": "Permission Queued",
|
||||
"toolCall.permission.requestedDiff.label": "Requested diff",
|
||||
"toolCall.permission.requestedDiff.withPath": "Requested diff · {path}",
|
||||
"toolCall.permission.queuedText": "Waiting for earlier permission responses.",
|
||||
"toolCall.permission.actions.allowOnce": "Allow Once",
|
||||
"toolCall.permission.actions.alwaysAllow": "Always Allow",
|
||||
"toolCall.permission.actions.deny": "Deny",
|
||||
"toolCall.permission.shortcuts.allowOnce": "Allow once",
|
||||
"toolCall.permission.shortcuts.alwaysAllow": "Always allow",
|
||||
"toolCall.permission.shortcuts.deny": "Deny",
|
||||
"toolCall.permission.errors.unableToUpdate": "Unable to update permission",
|
||||
|
||||
"permissionApproval.title": "Requests",
|
||||
"permissionApproval.empty": "No pending requests.",
|
||||
"permissionApproval.kind.permission": "Permission",
|
||||
"permissionApproval.kind.question": "Question",
|
||||
"permissionApproval.questionCount.one": "{count} question",
|
||||
"permissionApproval.questionCount.other": "{count} questions",
|
||||
"permissionApproval.status.active": "Active",
|
||||
"permissionApproval.actions.closeAriaLabel": "Close",
|
||||
"permissionApproval.actions.goToSession": "Go to Session",
|
||||
"permissionApproval.actions.loadingSession": "Loading…",
|
||||
"permissionApproval.actions.loadSession": "Load Session",
|
||||
"permissionApproval.actions.allowOnce": "Allow Once",
|
||||
"permissionApproval.actions.alwaysAllow": "Always Allow",
|
||||
"permissionApproval.actions.deny": "Deny",
|
||||
"permissionApproval.fallbackHint": "Load session for more information.",
|
||||
"permissionApproval.errors.unableToUpdatePermission": "Unable to update permission",
|
||||
|
||||
"toolCall.question.status.required": "Question Required",
|
||||
"toolCall.question.status.queued": "Question Queued",
|
||||
"toolCall.question.status.questions": "Questions",
|
||||
"toolCall.question.action.awaitingAnswers": "Awaiting answers...",
|
||||
"toolCall.question.title.questions": "Questions",
|
||||
"toolCall.question.title.askingQuestions": "Asking questions",
|
||||
"toolCall.question.type.one": "Question",
|
||||
"toolCall.question.type.other": "Questions",
|
||||
"toolCall.question.number": "Q{number}:",
|
||||
"toolCall.question.multiple": "Multiple",
|
||||
"toolCall.question.custom.title": "Type a custom answer",
|
||||
"toolCall.question.custom.label": "Custom answer",
|
||||
"toolCall.question.custom.placeholder": "Type your own answer",
|
||||
"toolCall.question.actions.submit": "Submit",
|
||||
"toolCall.question.actions.dismiss": "Dismiss",
|
||||
"toolCall.question.shortcuts.submit": "Submit",
|
||||
"toolCall.question.shortcuts.dismiss": "Dismiss",
|
||||
"toolCall.question.queuedText": "Waiting for earlier responses.",
|
||||
"toolCall.question.validation.answerAll": "Please answer all questions before submitting.",
|
||||
"toolCall.question.errors.unableToReply": "Unable to reply",
|
||||
"toolCall.question.errors.unableToDismiss": "Unable to dismiss",
|
||||
|
||||
"toolCall.task.action.delegating": "Delegating...",
|
||||
"toolCall.task.sections.prompt": "Prompt",
|
||||
"toolCall.task.sections.steps": "Steps",
|
||||
"toolCall.task.sections.output": "Output",
|
||||
"toolCall.task.steps.count": "{count} steps",
|
||||
"toolCall.task.meta.agentModel": "Agent: {agent} • Model: {model}",
|
||||
"toolCall.task.meta.agent": "Agent: {agent}",
|
||||
"toolCall.task.meta.model": "Model: {model}",
|
||||
|
||||
"toolCall.status.pending": "Pending",
|
||||
"toolCall.status.running": "Running",
|
||||
"toolCall.status.completed": "Completed",
|
||||
"toolCall.status.error": "Error",
|
||||
"toolCall.status.unknown": "Unknown",
|
||||
|
||||
"toolCall.applyPatch.action.preparing": "Preparing apply_patch...",
|
||||
"toolCall.applyPatch.title.withFileCount.one": "{tool} ({count} file)",
|
||||
"toolCall.applyPatch.title.withFileCount.other": "{tool} ({count} files)",
|
||||
"toolCall.applyPatch.fileFallback": "File {number}",
|
||||
} as const
|
||||
@@ -1,6 +1,7 @@
|
||||
import { marked } from "marked"
|
||||
import { createHighlighter, type Highlighter, bundledLanguages } from "shiki/bundle/full"
|
||||
import { getLogger } from "./logger"
|
||||
import { tGlobal } from "./i18n"
|
||||
|
||||
const log = getLogger("actions")
|
||||
|
||||
@@ -259,19 +260,20 @@ function setupRenderer(isDark: boolean) {
|
||||
// Use "text" as default when no language is specified
|
||||
const resolvedLang = lang && lang.trim() ? lang.trim() : "text"
|
||||
const escapedLang = escapeHtml(resolvedLang)
|
||||
const copyLabel = escapeHtml(tGlobal("markdown.copy"))
|
||||
|
||||
const header = `
|
||||
<div class="code-block-header">
|
||||
<span class="code-block-language">${escapedLang}</span>
|
||||
<button class="code-block-copy" data-code="${encodedCode}">
|
||||
<div class="code-block-header">
|
||||
<span class="code-block-language">${escapedLang}</span>
|
||||
<button class="code-block-copy" data-code="${encodedCode}">
|
||||
<svg class="copy-icon" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
|
||||
</svg>
|
||||
<span class="copy-text">Copy</span>
|
||||
</button>
|
||||
</div>
|
||||
`.trim()
|
||||
</svg>
|
||||
<span class="copy-text">${copyLabel}</span>
|
||||
</button>
|
||||
</div>
|
||||
`.trim()
|
||||
|
||||
if (highlightSuppressed) {
|
||||
return `<div class="markdown-code-block" data-language="${escapedLang}" data-code="${encodedCode}">${header}<pre><code class="language-${escapedLang}">${escapeHtml(decodedCode)}</code></pre></div>`
|
||||
|
||||
Reference in New Issue
Block a user