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:
Shantur Rathore
2026-01-26 12:26:12 +00:00
parent 33939f4096
commit 5b1e21345f
88 changed files with 2080 additions and 822 deletions

View File

@@ -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,

View 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

View 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

View 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

View 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

View 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

View File

@@ -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

View 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,
)

View 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": "Well 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

View 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 AIs 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

View 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

View 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

View 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>
}

View 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

View 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

View 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

View 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

View 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

View 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