diff --git a/apps/app/src/app/components/session/composer.tsx b/apps/app/src/app/components/session/composer.tsx
index 64e8fc554..c9d2531be 100644
--- a/apps/app/src/app/components/session/composer.tsx
+++ b/apps/app/src/app/components/session/composer.tsx
@@ -7,6 +7,7 @@ import ComposerNotice, { type ComposerNotice as ComposerNoticeData } from "./com
import type { ComposerAttachment, ComposerDraft, ComposerPart, PromptMode, SlashCommandOption } from "../../types";
import { perfNow, recordPerfLog } from "../../lib/perf-log";
+import { t } from "../../../i18n";
type MentionOption = {
id: string;
@@ -516,7 +517,7 @@ export default function Composer(props: ComposerProps) {
span.dataset.pasteId = part.id;
span.dataset.pasteLabel = part.label;
span.dataset.pasteLines = String(part.lines);
- span.title = "Click to expand pasted text";
+ span.title = t("composer.expand_pasted");
span.className =
"inline-flex items-center rounded-full px-2 py-0.5 text-[11px] font-semibold bg-gray-3 text-gray-10 border border-gray-6 cursor-pointer hover:bg-gray-4 hover:text-gray-11";
return span;
@@ -1058,7 +1059,7 @@ export default function Composer(props: ComposerProps) {
const addAttachments = async (files: File[]) => {
if (attachmentsDisabled()) {
props.onNotice({
- title: props.attachmentsDisabledReason ?? "Attachments are unavailable.",
+ title: props.attachmentsDisabledReason ?? t("composer.attachments_unavailable"),
tone: "warning",
});
return;
@@ -1078,7 +1079,7 @@ export default function Composer(props: ComposerProps) {
for (const file of supportedFiles) {
if (file.size > MAX_ATTACHMENT_BYTES) {
props.onNotice({
- title: `${file.name} exceeds the 8MB limit.`,
+ title: t("composer.file_exceeds_limit", undefined, { name: file.name }),
tone: "warning",
});
continue;
@@ -1089,7 +1090,7 @@ export default function Composer(props: ComposerProps) {
const estimatedJsonBytes = estimateInlineAttachmentBytes(processed);
if (estimatedJsonBytes > MAX_ATTACHMENT_BYTES) {
props.onNotice({
- title: `${file.name} is too large after encoding. Try a smaller image.`,
+ title: t("composer.file_too_large_encoding", undefined, { name: file.name }),
tone: "warning",
});
continue;
@@ -1105,7 +1106,7 @@ export default function Composer(props: ComposerProps) {
});
} catch (error) {
props.onNotice({
- title: error instanceof Error ? error.message : "Failed to read attachment",
+ title: error instanceof Error ? error.message : t("composer.failed_read_attachment"),
tone: "error",
});
}
@@ -1221,29 +1222,29 @@ export default function Composer(props: ComposerProps) {
props.onNotice({
title:
links.length === 1
- ? `Uploaded ${links[0].name} to the shared folder and inserted a link.`
- : `Uploaded ${links.length} files to the shared folder and inserted links.`,
+ ? t("composer.uploaded_single_file", undefined, { name: links[0].name })
+ : t("composer.uploaded_multiple_files", undefined, { count: links.length }),
tone: "success",
});
return;
}
}
props.onNotice({
- title: "Couldn't upload to the shared folder. Inserted local links instead.",
+ title: t("composer.upload_failed_local_links"),
tone: "warning",
});
}
const text = formatLinks(fallbackLinks());
if (!text) {
- props.onNotice({ title: "Unsupported attachment type.", tone: "warning" });
+ props.onNotice({ title: t("composer.unsupported_attachment_type"), tone: "warning" });
return;
}
insertPlainTextAtSelection(text);
updateMentionQuery();
updateSlashQuery();
emitDraftChange();
- props.onNotice({ title: "Inserted links for unsupported files.", tone: "info" });
+ props.onNotice({ title: t("composer.inserted_links_unsupported"), tone: "info" });
};
const handlePaste = (event: ClipboardEvent) => {
@@ -1277,10 +1278,9 @@ export default function Composer(props: ComposerProps) {
const hasAbsoluteWindows = /(^|\s)[a-zA-Z]:\\/.test(trimmedForCheck);
if (hasFileUrl || hasAbsolutePosix || hasAbsoluteWindows) {
props.onNotice({
- title:
- "This is a remote worker. Sandboxes are remote too. To share files with it, upload them to the Shared folder in the sidebar.",
+ title: t("composer.remote_worker_paste_warning"),
tone: "warning",
- actionLabel: props.onUploadInboxFiles ? "Upload to shared folder" : undefined,
+ actionLabel: props.onUploadInboxFiles ? t("composer.upload_to_shared_folder") : undefined,
onAction: props.onUploadInboxFiles ? () => inboxFileInputRef?.click() : undefined,
});
}
@@ -1572,7 +1572,7 @@ export default function Composer(props: ComposerProps) {
event.preventDefault()}>
No matches found.
}
+ fallback={{t("composer.no_matches")}
}
>
{(option: MentionOption) => {
@@ -1635,7 +1635,7 @@ export default function Composer(props: ComposerProps) {
when={slashFiltered().length}
fallback={
- {slashLoading() ? "Loading commands..." : "No commands found."}
+ {slashLoading() ? t("composer.loading_commands") : t("composer.no_commands")}
}
>
@@ -1662,7 +1662,7 @@ export default function Composer(props: ComposerProps) {
- {cmd.source === "skill" ? "Skill" : cmd.source === "mcp" ? "MCP" : ""}
+ {cmd.source === "skill" ? t("composer.skill_source") : cmd.source === "mcp" ? "MCP" : ""}
@@ -1697,7 +1697,7 @@ export default function Composer(props: ComposerProps) {
{attachment.name}
- {attachment.kind === "image" ? "Image" : attachment.mimeType || "File"}
+ {attachment.kind === "image" ? t("composer.image_kind") : attachment.mimeType || t("composer.file_kind")}
@@ -524,9 +525,9 @@ export default function WorkspaceSessionList(props: Props) {
getWorkspaceTaskLoadErrorDisplay(workspace(), group.error);
const statusLabel = () => {
if (group.status === "error") return taskLoadError().label;
- if (isConnectionActionBusy()) return "Connecting";
+ if (isConnectionActionBusy()) return t("workspace_list.connecting");
if (!props.developerMode) return "";
- if (props.selectedWorkspaceId === workspace().id) return "Selected";
+ if (props.selectedWorkspaceId === workspace().id) return t("workspace.selected");
return workspaceKindLabel(workspace());
};
const statusTone = () => {
@@ -600,7 +601,7 @@ export default function WorkspaceSessionList(props: Props) {
props.onCreateTaskInWorkspace(workspace().id);
}}
disabled={props.newTaskDisabled}
- aria-label="New task"
+ aria-label={t("session.new_task")}
>
@@ -616,7 +617,7 @@ export default function WorkspaceSessionList(props: Props) {
: workspace().id,
);
}}
- aria-label="Workspace options"
+ aria-label={t("workspace_list.workspace_options")}
>
@@ -627,8 +628,8 @@ export default function WorkspaceSessionList(props: Props) {
class="rounded-md p-1 text-gray-9 hover:bg-gray-3/80 hover:text-gray-11"
aria-label={
isWorkspaceExpanded(workspace().id)
- ? "Collapse"
- : "Expand"
+ ? t("sidebar.collapse")
+ : t("sidebar.expand")
}
onClick={(event) => {
event.stopPropagation();
@@ -659,7 +660,7 @@ export default function WorkspaceSessionList(props: Props) {
setWorkspaceMenuId(null);
}}
>
- Edit name
+ {t("workspace_list.edit_name")}
- Share...
+ {t("workspace_list.share")}
- {revealLabel}
+ {revealLabel()}
@@ -696,7 +697,7 @@ export default function WorkspaceSessionList(props: Props) {
}}
disabled={isConnectionActionBusy()}
>
- Recover
+ {t("workspace_list.recover")}
- Test connection
+ {t("workspace_list.test_connection")}
- Edit connection
+ {t("workspace_list.edit_connection")}
- Remove workspace
+ {t("workspace_list.remove_workspace")}
@@ -841,10 +842,10 @@ export default function WorkspaceSessionList(props: Props) {
disabled={props.newTaskDisabled}
>
- No tasks yet.
+ {t("workspace.no_tasks")}
- + New task
+ {t("workspace.new_task_inline")}
@@ -875,7 +876,7 @@ export default function WorkspaceSessionList(props: Props) {
}
>
- Loading tasks...
+ {t("workspace.loading_tasks")}
@@ -895,7 +896,7 @@ export default function WorkspaceSessionList(props: Props) {
onClick={props.onOpenCreateWorkspace}
>
- Add workspace
+ {t("workspace_list.add_workspace")}
diff --git a/apps/app/src/app/components/status-bar.tsx b/apps/app/src/app/components/status-bar.tsx
index 8d229bf5c..d2013cbd1 100644
--- a/apps/app/src/app/components/status-bar.tsx
+++ b/apps/app/src/app/components/status-bar.tsx
@@ -1,6 +1,7 @@
import { Show, createMemo } from "solid-js";
import { MessageCircle, Settings } from "lucide-solid";
+import { t } from "../../i18n";
import { useConnections } from "../connections/provider";
import type { OpenworkServerStatus } from "../lib/openwork-server";
@@ -47,19 +48,19 @@ export default function StatusBar(props: StatusBarProps) {
if (props.clientConnected) {
const detailBits: string[] = [];
if (providers > 0) {
- detailBits.push(`${providers} provider${providers === 1 ? "" : "s"} connected`);
+ detailBits.push(t("status.providers_connected", undefined, { count: providers, plural: providers === 1 ? "" : "s" }));
}
if (mcp > 0) {
- detailBits.push(`${mcp} MCP connected`);
+ detailBits.push(t("status.mcp_connected", undefined, { count: mcp }));
}
if (!detailBits.length) {
- detailBits.push("Ready for new tasks");
+ detailBits.push(t("status.ready_for_tasks"));
}
if (props.developerMode) {
- detailBits.push("Developer mode");
+ detailBits.push(t("status.developer_mode"));
}
return {
- label: "OpenWork Ready",
+ label: t("status.openwork_ready"),
detail: detailBits.join(" · "),
dotClass: "bg-green-9",
pingClass: "bg-green-9/45 animate-ping",
@@ -69,11 +70,11 @@ export default function StatusBar(props: StatusBarProps) {
if (props.openworkServerStatus === "limited") {
return {
- label: "Limited Mode",
+ label: t("status.limited_mode"),
detail:
mcp > 0
- ? `${mcp} MCP connected · reconnect for full features`
- : "Reconnect to restore full OpenWork features",
+ ? t("status.limited_mcp_hint", undefined, { count: mcp })
+ : t("status.limited_hint"),
dotClass: "bg-amber-9",
pingClass: "bg-amber-9/35",
pulse: false,
@@ -81,8 +82,8 @@ export default function StatusBar(props: StatusBarProps) {
}
return {
- label: "Disconnected",
- detail: "Open settings to reconnect",
+ label: t("status.disconnected_label"),
+ detail: t("status.disconnected_hint"),
dotClass: "bg-red-9",
pingClass: "bg-red-9/35",
pulse: false,
@@ -108,19 +109,19 @@ export default function StatusBar(props: StatusBarProps) {
type="button"
class="inline-flex h-8 items-center gap-1.5 rounded-md px-2 text-dls-secondary transition-colors hover:bg-dls-hover hover:text-dls-text"
onClick={props.onSendFeedback}
- title="Send feedback"
- aria-label="Send feedback"
+ title={t("status.send_feedback")}
+ aria-label={t("status.send_feedback")}
>
- Feedback
+ {t("status.feedback")}
diff --git a/apps/app/src/app/pages/session.tsx b/apps/app/src/app/pages/session.tsx
index 1cefd834a..80e367d10 100644
--- a/apps/app/src/app/pages/session.tsx
+++ b/apps/app/src/app/pages/session.tsx
@@ -9,7 +9,7 @@ import {
onCleanup,
onMount,
} from "solid-js";
-import { t, currentLocale } from "../../i18n";
+import { t } from "../../i18n";
import type { Agent, Part, Session } from "@opencode-ai/sdk/v2/client";
import type {
ComposerDraft,
@@ -269,10 +269,10 @@ type CommandPaletteMode = "root" | "sessions";
function describePermissionRequest(permission: PendingPermission | null) {
if (!permission) {
return {
- title: "Permission Required",
- message: "OpenCode is requesting permission to continue.",
+ title: t("session.permission_required"),
+ message: t("session.permission_message"),
permissionLabel: "",
- scopeLabel: "Scope",
+ scopeLabel: t("session.scope_label"),
scopeValue: "",
isDoomLoop: false,
note: null as string | null,
@@ -286,21 +286,21 @@ function describePermissionRequest(permission: PendingPermission | null) {
? permission.metadata.tool
: null;
return {
- title: "Doom Loop Detected",
- message: "OpenCode detected repeated tool calls with identical input and is asking whether it should continue after repeated failures.",
- permissionLabel: "Doom Loop",
- scopeLabel: tool ? "Tool" : "Repeated call",
- scopeValue: tool ?? (patterns.length ? patterns.join(", ") : "Repeated tool call"),
+ title: t("session.doom_loop_title"),
+ message: t("session.doom_loop_message"),
+ permissionLabel: t("session.doom_loop_label"),
+ scopeLabel: tool ? t("session.doom_loop_tool_label") : t("session.doom_loop_repeated_call_label"),
+ scopeValue: tool ?? (patterns.length ? patterns.join(", ") : t("session.doom_loop_repeated_tool_call")),
isDoomLoop: true,
- note: "Reject to stop the loop, or allow if you want the agent to keep trying.",
+ note: t("session.doom_loop_note"),
};
}
return {
- title: "Permission Required",
- message: "OpenCode is requesting permission to continue.",
+ title: t("session.permission_required"),
+ message: t("session.permission_message"),
permissionLabel: permission.permission,
- scopeLabel: "Scope",
+ scopeLabel: t("session.scope_label"),
scopeValue: patterns.join(", "),
isDoomLoop: false,
note: null as string | null,
@@ -425,7 +425,7 @@ export default function SessionView(props: SessionViewProps) {
};
const agentLabel = createMemo(() => {
- const name = sessionActions.selectedSessionAgent() ?? "Default agent";
+ const name = sessionActions.selectedSessionAgent() ?? t("session.default_agent");
return name.charAt(0).toUpperCase() + name.slice(1);
});
const workspaceLabel = (workspace: WorkspaceInfo) =>
@@ -433,7 +433,7 @@ export default function SessionView(props: SessionViewProps) {
workspace.openworkWorkspaceName?.trim() ||
workspace.name?.trim() ||
workspace.path?.trim() ||
- "Workspace";
+ t("session.workspace_label");
const todoList = createMemo(() =>
props.todos.filter((todo) => todo.content.trim()),
);
@@ -627,11 +627,11 @@ export default function SessionView(props: SessionViewProps) {
const activeSearchPositionLabel = createMemo(() => {
const hits = searchHits();
- if (!hits.length) return "No matches";
+ if (!hits.length) return t("session.no_matches");
const size = hits.length;
const raw = activeSearchHitIndex();
const index = ((raw % size) + size) % size;
- return `${index + 1} of ${size}`;
+ return t("session.search_position", undefined, { current: index + 1, total: size });
});
const searchActive = createMemo(
@@ -1020,7 +1020,7 @@ export default function SessionView(props: SessionViewProps) {
: "";
return {
ok: false as const,
- reason: `${lastError instanceof Error ? lastError.message : "File open failed"}${suffix}`,
+ reason: `${lastError instanceof Error ? lastError.message : t("session.file_open_failed")}${suffix}`,
};
};
@@ -1030,11 +1030,11 @@ export default function SessionView(props: SessionViewProps) {
if (!workspace || workspace.workspaceType !== "local") return;
const target = workspace.path?.trim() ?? "";
if (!target) {
- showStatusToast("Workspace path is unavailable.", "warning");
+ showStatusToast(t("session.workspace_path_unavailable"), "warning");
return;
}
if (!isTauriRuntime()) {
- showStatusToast("Reveal is available in the desktop app.", "warning");
+ showStatusToast(t("session.reveal_desktop_only"), "warning");
return;
}
try {
@@ -1047,14 +1047,14 @@ export default function SessionView(props: SessionViewProps) {
}
} catch (error) {
const message =
- error instanceof Error ? error.message : "Unable to reveal workspace";
+ error instanceof Error ? error.message : t("session.unable_to_reveal");
showStatusToast(message, "error");
}
};
const todoLabel = createMemo(() => {
const total = todoCount();
if (!total) return "";
- return `${todoCompletedCount()} out of ${total} tasks completed`;
+ return t("session.todo_progress", undefined, { completed: todoCompletedCount(), total });
});
const shareWorkspaceState = createShareWorkspaceState({
workspaces: () => props.workspaces,
@@ -1073,9 +1073,9 @@ export default function SessionView(props: SessionViewProps) {
const attachmentsDisabledReason = createMemo(() => {
if (attachmentsEnabled()) return null;
if (props.openworkServerStatus === "limited") {
- return "Add a server token to attach files.";
+ return t("session.attachments_add_token");
}
- return "Connect to OpenWork server to attach files.";
+ return t("session.attachments_connect_server");
});
onCleanup(() => {
@@ -1151,12 +1151,12 @@ export default function SessionView(props: SessionViewProps) {
if (!trimmed) return;
if (props.selectedWorkspaceDisplay.workspaceType === "remote") {
- showStatusToast("File open is unavailable for remote workspaces.", "warning");
+ showStatusToast(t("session.file_open_remote_unavailable"), "warning");
return;
}
if (!isTauriRuntime()) {
- showStatusToast("File open is available in the desktop app.", "warning");
+ showStatusToast(t("session.file_open_desktop_only"), "warning");
return;
}
@@ -1170,7 +1170,7 @@ export default function SessionView(props: SessionViewProps) {
},
);
if (!result.ok && result.reason === "missing-root") {
- showStatusToast("Pick a workspace to open files.", "warning");
+ showStatusToast(t("session.pick_workspace_to_open"), "warning");
return;
}
if (!result.ok) {
@@ -1179,7 +1179,7 @@ export default function SessionView(props: SessionViewProps) {
}
} catch (error) {
const message =
- error instanceof Error ? error.message : "Unable to open file";
+ error instanceof Error ? error.message : t("session.unable_to_open_file");
showStatusToast(message, "error");
}
};
@@ -1199,7 +1199,7 @@ export default function SessionView(props: SessionViewProps) {
return sorted;
} catch (error) {
const message =
- error instanceof Error ? error.message : "Failed to load agents";
+ error instanceof Error ? error.message : t("session.failed_to_load_agents");
setAgentPickerError(message);
setAgentOptions([]);
return [];
@@ -1326,13 +1326,13 @@ export default function SessionView(props: SessionViewProps) {
const compactionStatusDetail = createMemo(() => {
if (!showCompactionIndicator()) return "";
return props.sessionCompactionState?.mode === "auto"
- ? "OpenCode is auto-compacting this session"
- : "OpenCode is compacting this session";
+ ? t("session.compacting_auto")
+ : t("session.compacting_manual");
});
const statusBarCopy = createMemo(() => {
if (showCompactionIndicator()) {
return {
- label: "Compacting Context",
+ label: t("session.status_compacting"),
detail: compactionStatusDetail(),
dotClass: "bg-blue-9",
pingClass: "bg-blue-9/35 animate-ping",
@@ -1342,7 +1342,7 @@ export default function SessionView(props: SessionViewProps) {
if (showRunIndicator()) {
return {
- label: "Session Active",
+ label: t("session.status_active"),
detail: undefined,
dotClass: "bg-green-9",
pingClass: "bg-green-9/45 animate-ping",
@@ -1355,7 +1355,7 @@ export default function SessionView(props: SessionViewProps) {
if (props.selectedSessionId) {
return {
- label: "Session Ready",
+ label: t("session.status_ready_session"),
detail: undefined,
dotClass: "bg-green-9",
pingClass: "bg-green-9/35",
@@ -1364,7 +1364,7 @@ export default function SessionView(props: SessionViewProps) {
}
return {
- label: "Ready",
+ label: t("session.status_ready"),
detail: undefined,
dotClass: "bg-gray-8",
pingClass: "bg-green-9/35",
@@ -1378,7 +1378,7 @@ export default function SessionView(props: SessionViewProps) {
(startedAt, previous) => {
if (!startedAt || startedAt === previous) return;
if (props.sessionCompactionState?.mode === "manual") return;
- showStatusToast("OpenCode started compacting the session context.", "info");
+ showStatusToast(t("session.compaction_started"), "info");
},
),
);
@@ -1389,7 +1389,7 @@ export default function SessionView(props: SessionViewProps) {
(finishedAt, previous) => {
if (!finishedAt || finishedAt === previous) return;
if (props.sessionCompactionState?.mode === "manual") return;
- showStatusToast("OpenCode finished compacting the session context.", "success");
+ showStatusToast(t("session.compaction_finished"), "success");
},
),
);
@@ -1437,30 +1437,30 @@ export default function SessionView(props: SessionViewProps) {
const tool = typeof record.tool === "string" ? record.tool : "";
switch (tool) {
case "task":
- return "Delegating";
+ return t("session.status_delegating");
case "todowrite":
case "todoread":
- return "Planning";
+ return t("session.status_planning");
case "read":
- return "Gathering context";
+ return t("session.status_gathering_context");
case "list":
case "grep":
case "glob":
- return "Searching codebase";
+ return t("session.status_searching_codebase");
case "webfetch":
- return "Searching the web";
+ return t("session.status_searching_web");
case "edit":
case "write":
case "apply_patch":
- return "Writing file";
+ return t("session.status_writing_file");
case "bash":
- return "Running shell";
+ return t("session.status_running_shell");
default:
- return "Working";
+ return t("session.status_working");
}
}
if (part.type === "reasoning") {
- return "Thinking";
+ return t("session.status_thinking");
}
if (part.type === "text") {
return null;
@@ -1471,7 +1471,7 @@ export default function SessionView(props: SessionViewProps) {
const thinkingStatus = createMemo(() => {
const status = computeStatusFromPart(latestRunPart());
if (status) return status;
- if (runPhase() === "thinking") return "Thinking";
+ if (runPhase() === "thinking") return t("session.status_thinking");
return null;
});
@@ -1516,15 +1516,15 @@ export default function SessionView(props: SessionViewProps) {
const runLabel = createMemo(() => {
switch (runPhase()) {
case "sending":
- return "Sending";
+ return t("session.phase_sending");
case "retrying":
- return "Retrying";
+ return t("session.phase_retrying");
case "responding":
- return "Responding";
+ return t("session.phase_responding");
case "thinking":
- return "Thinking";
+ return t("session.status_thinking");
case "error":
- return "Run failed";
+ return t("session.phase_run_failed");
default:
return "";
}
@@ -1848,17 +1848,17 @@ export default function SessionView(props: SessionViewProps) {
const cancelRun = async () => {
if (abortBusy()) return;
if (!props.selectedSessionId) {
- showStatusToast("No session selected", "warning");
+ showStatusToast(t("session.no_session_selected"), "warning");
return;
}
setAbortBusy(true);
- showStatusToast("Stopping the run...", "info");
+ showStatusToast(t("session.stopping_run"), "info");
try {
await sessionActions.abortSession(props.selectedSessionId);
- showStatusToast("Stopped.", "success");
+ showStatusToast(t("session.stopped"), "success");
} catch (error) {
- const message = error instanceof Error ? error.message : "Failed to stop";
+ const message = error instanceof Error ? error.message : t("session.failed_to_stop");
showStatusToast(message, "error");
} finally {
setAbortBusy(false);
@@ -1868,13 +1868,13 @@ export default function SessionView(props: SessionViewProps) {
const retryRun = async () => {
const text = sessionActions.lastPromptSent().trim();
if (!text) {
- showStatusToast("Nothing to retry yet", "warning");
+ showStatusToast(t("session.nothing_to_retry"), "warning");
return;
}
if (abortBusy()) return;
setAbortBusy(true);
- showStatusToast("Trying again...", "info");
+ showStatusToast(t("session.trying_again"), "info");
try {
if (showRunIndicator() && props.selectedSessionId) {
await sessionActions.abortSession(props.selectedSessionId);
@@ -1967,18 +1967,18 @@ export default function SessionView(props: SessionViewProps) {
const undoLastMessage = async () => {
if (historyActionBusy()) return;
if (!canUndoLastMessage()) {
- showStatusToast("Nothing to undo yet.", "warning");
+ showStatusToast(t("session.nothing_to_undo"), "warning");
return;
}
setHistoryActionBusy("undo");
try {
await sessionActions.undoLastUserMessage();
- showStatusToast("Reverted the last user message.", "success");
+ showStatusToast(t("session.reverted_last_message"), "success");
} catch (error) {
const message =
error instanceof Error ? error.message : props.safeStringify(error);
- showStatusToast(message || "Failed to undo", "error");
+ showStatusToast(message || t("session.failed_to_undo"), "error");
} finally {
setHistoryActionBusy(null);
}
@@ -1987,18 +1987,18 @@ export default function SessionView(props: SessionViewProps) {
const redoLastMessage = async () => {
if (historyActionBusy()) return;
if (!canRedoLastMessage()) {
- showStatusToast("Nothing to redo.", "warning");
+ showStatusToast(t("session.nothing_to_redo"), "warning");
return;
}
setHistoryActionBusy("redo");
try {
await sessionActions.redoLastUserMessage();
- showStatusToast("Restored the reverted message.", "success");
+ showStatusToast(t("session.restored_message"), "success");
} catch (error) {
const message =
error instanceof Error ? error.message : props.safeStringify(error);
- showStatusToast(message || "Failed to redo", "error");
+ showStatusToast(message || t("session.failed_to_redo"), "error");
} finally {
setHistoryActionBusy(null);
}
@@ -2007,24 +2007,24 @@ export default function SessionView(props: SessionViewProps) {
const compactSessionHistory = async () => {
if (historyActionBusy()) return;
if (!canCompactSession()) {
- showStatusToast(t("app.error_compact_empty", currentLocale()), "warning");
+ showStatusToast(t("session.nothing_to_compact"), "warning");
return;
}
const sessionID = props.selectedSessionId;
const startedAt = perfNow();
setHistoryActionBusy("compact");
- showStatusToast("Compacting session context...", "info");
+ showStatusToast(t("session.compacting"), "info");
try {
await sessionActions.compactCurrentSession();
- showStatusToast("Session compacted.", "success");
+ showStatusToast(t("session.compacted"), "success");
finishPerf(props.developerMode, "session.compact", "ui-done", startedAt, {
sessionID,
});
} catch (error) {
const message =
error instanceof Error ? error.message : props.safeStringify(error);
- showStatusToast(message || "Failed to compact session", "error");
+ showStatusToast(message || t("session.failed_to_compact"), "error");
finishPerf(
props.developerMode,
"session.compact",
@@ -2087,7 +2087,7 @@ export default function SessionView(props: SessionViewProps) {
const lastMsg = chatContainerEl?.querySelector(
'[data-message-role="assistant"]:last-child',
);
- triggerFlyout(lastMsg ?? null, "sidebar-progress", "New Task", "check");
+ triggerFlyout(lastMsg ?? null, "sidebar-progress", t("session.flyout_new_task"), "check");
}
setPrevTodoCount(count);
});
@@ -2103,7 +2103,7 @@ export default function SessionView(props: SessionViewProps) {
triggerFlyout(
lastMsg ?? null,
"sidebar-context",
- "File Modified",
+ t("session.flyout_file_modified"),
"folder",
);
}
@@ -2162,9 +2162,9 @@ export default function SessionView(props: SessionViewProps) {
return sessionTitleForId(pending.sessionId);
});
const sessionHeaderTitle = createMemo(() => {
- if (showWorkspaceSetupEmptyState()) return "Create or connect a workspace";
+ if (showWorkspaceSetupEmptyState()) return t("session.create_or_connect_workspace");
if (showPendingSessionTransition()) {
- return pendingSessionTransitionTitle() || "Loading session";
+ return pendingSessionTransitionTitle() || t("session.loading_session");
}
return selectedSessionTitle() || DEFAULT_SESSION_TITLE;
});
@@ -2246,7 +2246,7 @@ export default function SessionView(props: SessionViewProps) {
const openRenameModal = (options?: { returnFocusToComposer?: boolean }) => {
const sessionId = props.selectedSessionId;
if (!sessionId) {
- showStatusToast("No session selected", "warning");
+ showStatusToast(t("session.no_session_selected"), "warning");
if (options?.returnFocusToComposer) {
focusComposer();
}
@@ -2284,7 +2284,7 @@ export default function SessionView(props: SessionViewProps) {
const openDeleteSessionModal = () => {
const sessionId = props.selectedSessionId;
if (!sessionId) {
- showStatusToast("No session selected", "warning");
+ showStatusToast(t("session.no_session_selected"), "warning");
return;
}
setDeleteSessionId(sessionId);
@@ -2306,13 +2306,13 @@ export default function SessionView(props: SessionViewProps) {
await sessionActions.deleteSessionById(sessionId);
setDeleteSessionOpen(false);
setDeleteSessionId(null);
- showStatusToast("Session deleted", "success");
+ showStatusToast(t("session.deleted"), "success");
// Route away from the deleted session id.
props.setView("session");
} catch (error) {
const message =
error instanceof Error ? error.message : props.safeStringify(error);
- showStatusToast(message || "Failed to delete session", "error");
+ showStatusToast(message || t("session.failed_to_delete"), "error");
} finally {
setDeleteSessionBusy(false);
}
@@ -2321,7 +2321,7 @@ export default function SessionView(props: SessionViewProps) {
const requireSessionId = () => {
const sessionId = props.selectedSessionId;
if (!sessionId) {
- showStatusToast("No session selected", "warning");
+ showStatusToast(t("session.no_session_selected"), "warning");
return null;
}
return sessionId;
@@ -2360,7 +2360,7 @@ export default function SessionView(props: SessionViewProps) {
methodIndex?: number,
): Promise => {
if (providerAuthActionBusy()) {
- throw new Error("Provider auth is already in progress.");
+ throw new Error(t("session.provider_auth_in_progress"));
}
setProviderAuthActionBusy(true);
try {
@@ -2384,12 +2384,12 @@ export default function SessionView(props: SessionViewProps) {
code,
);
if (result.connected) {
- showStatusToast(result.message || "Provider connected", "success");
+ showStatusToast(result.message || t("session.provider_connected"), "success");
props.closeProviderAuthModal();
}
return result;
} catch (error) {
- const message = error instanceof Error ? error.message : "OAuth failed";
+ const message = error instanceof Error ? error.message : t("session.oauth_failed");
showStatusToast(message, "error");
return { connected: false };
} finally {
@@ -2405,11 +2405,11 @@ export default function SessionView(props: SessionViewProps) {
setProviderAuthActionBusy(true);
try {
const message = await props.submitProviderApiKey(providerId, apiKey);
- showStatusToast(message || "API key saved", "success");
+ showStatusToast(message || t("session.api_key_saved"), "success");
props.closeProviderAuthModal();
} catch (error) {
const message =
- error instanceof Error ? error.message : "Failed to save API key";
+ error instanceof Error ? error.message : t("session.failed_to_save_api_key");
showStatusToast(message, "error");
} finally {
setProviderAuthActionBusy(false);
@@ -2439,7 +2439,7 @@ export default function SessionView(props: SessionViewProps) {
if (!client || !workspaceId) {
if (notify) {
showComposerNotice({
- title: "Connect to the OpenWork server to upload files to the shared folder.",
+ title: t("session.upload_connect_server"),
tone: "warning",
});
}
@@ -2451,7 +2451,7 @@ export default function SessionView(props: SessionViewProps) {
files.length === 1 ? (files[0]?.name ?? "file") : `${files.length} files`;
if (notify) {
showComposerNotice({
- title: `Uploading ${label} to the shared folder...`,
+ title: t("session.uploading_to_shared_folder", undefined, { label }),
tone: "info",
});
}
@@ -2470,8 +2470,8 @@ export default function SessionView(props: SessionViewProps) {
.join(", ");
showComposerNotice({
title: summary
- ? `Uploaded to the shared folder: ${summary}`
- : "Uploaded to the shared folder.",
+ ? t("session.uploaded_with_summary", undefined, { summary })
+ : t("session.uploaded_to_shared_folder"),
tone: "success",
});
}
@@ -2481,7 +2481,7 @@ export default function SessionView(props: SessionViewProps) {
const message =
error instanceof Error
? error.message
- : "Shared folder upload failed";
+ : t("session.shared_folder_upload_failed");
showComposerNotice({ title: message, tone: "error" });
}
return [];
@@ -2532,9 +2532,9 @@ export default function SessionView(props: SessionViewProps) {
const items: CommandPaletteItem[] = [
{
id: "new-session",
- title: "Create new session",
- detail: "Start a fresh task in the current workspace",
- meta: "Create",
+ title: t("session.cmd_new_session_title"),
+ detail: t("session.cmd_new_session_detail"),
+ meta: t("session.cmd_new_session_meta"),
action: () => {
closeCommandPalette();
void Promise.resolve(sessionActions.createSessionAndOpen())
@@ -2546,18 +2546,18 @@ export default function SessionView(props: SessionViewProps) {
const message =
error instanceof Error
? error.message
- : "Failed to create session";
+ : t("session.failed_to_create_session");
showStatusToast(message, "error");
});
},
},
{
id: "rename-session",
- title: "Rename current session",
+ title: t("session.cmd_rename_title"),
detail:
selectedSessionTitle().trim() ||
- "Give your selected session a clearer name",
- meta: "Rename",
+ t("session.cmd_rename_detail_fallback"),
+ meta: t("session.cmd_rename_meta"),
action: () => {
closeCommandPalette();
openRenameModal({ returnFocusToComposer: true });
@@ -2565,11 +2565,11 @@ export default function SessionView(props: SessionViewProps) {
},
{
id: "compact-session",
- title: "Compact Conversation",
+ title: t("session.cmd_compact_title"),
detail: canCompactSession()
- ? "Send a compact instruction to OpenCode for this session"
- : "No user messages to compact yet",
- meta: "Compact",
+ ? t("session.cmd_compact_detail")
+ : t("session.cmd_compact_detail_empty"),
+ meta: t("session.cmd_compact_meta"),
action: () => {
closeCommandPalette();
void compactSessionHistory();
@@ -2577,9 +2577,9 @@ export default function SessionView(props: SessionViewProps) {
},
{
id: "sessions",
- title: "Search sessions",
- detail: `${totalSessionCount().toLocaleString()} available across workspaces`,
- meta: "Jump",
+ title: t("session.cmd_sessions_title"),
+ detail: t("session.cmd_sessions_detail", undefined, { count: totalSessionCount().toLocaleString() }),
+ meta: t("session.cmd_sessions_meta"),
action: () => {
setCommandPaletteMode("sessions");
setCommandPaletteQuery("");
@@ -2589,9 +2589,9 @@ export default function SessionView(props: SessionViewProps) {
},
{
id: "model",
- title: "Change model",
- detail: `${modelControls.selectedSessionModelLabel() || "Model"} · ${modelControls.sessionModelVariantLabel()}`,
- meta: "Open",
+ title: t("session.cmd_model_title"),
+ detail: t("session.cmd_model_detail", undefined, { model: modelControls.selectedSessionModelLabel() || t("session.cmd_model_fallback"), variant: modelControls.sessionModelVariantLabel() }),
+ meta: t("session.cmd_model_meta"),
action: () => {
closeCommandPalette();
modelControls.openSessionModelPicker({ returnFocusTarget: "composer" });
@@ -2599,9 +2599,9 @@ export default function SessionView(props: SessionViewProps) {
},
{
id: "provider",
- title: "Connect provider",
- detail: "Open provider connection flow",
- meta: "Open",
+ title: t("session.cmd_provider_title"),
+ detail: t("session.cmd_provider_detail"),
+ meta: t("session.cmd_provider_meta"),
action: () => {
closeCommandPalette();
void props
@@ -2610,7 +2610,7 @@ export default function SessionView(props: SessionViewProps) {
const message =
error instanceof Error
? error.message
- : "Failed to load providers";
+ : t("session.failed_to_load_providers");
showStatusToast(message, "error");
focusComposer();
});
@@ -2639,8 +2639,8 @@ export default function SessionView(props: SessionViewProps) {
detail: item.workspaceTitle,
meta:
item.workspaceId === props.selectedWorkspaceId
- ? "Current workspace"
- : "Switch",
+ ? t("session.cmd_current_workspace")
+ : t("session.cmd_switch"),
action: () => {
closeCommandPalette();
openSessionFromList(item.workspaceId, item.sessionId, {
@@ -2658,14 +2658,14 @@ export default function SessionView(props: SessionViewProps) {
const commandPaletteTitle = createMemo(() => {
const mode = commandPaletteMode();
- if (mode === "sessions") return "Search sessions";
- return "Quick actions";
+ if (mode === "sessions") return t("session.palette_title_sessions");
+ return t("session.palette_title_actions");
});
const commandPalettePlaceholder = createMemo(() => {
const mode = commandPaletteMode();
- if (mode === "sessions") return "Find by session title or workspace";
- return "Search actions";
+ if (mode === "sessions") return t("session.palette_placeholder_sessions");
+ return t("session.palette_placeholder_actions");
});
createEffect(
@@ -2708,13 +2708,13 @@ export default function SessionView(props: SessionViewProps) {
const updatePillLabel = createMemo(() => {
const state = props.updateStatus?.state;
if (state === "ready") {
- return props.anyActiveRuns ? "Update ready" : "Install update";
+ return props.anyActiveRuns ? t("session.update_ready") : t("session.install_update");
}
if (state === "downloading") {
const percent = updateDownloadPercent();
- return percent == null ? "Downloading" : `Downloading ${percent}%`;
+ return percent == null ? t("session.downloading") : t("session.downloading_percent", undefined, { percent });
}
- return "Update available";
+ return t("session.update_available");
});
const updatePillButtonTone = createMemo(() => {
@@ -2772,11 +2772,11 @@ export default function SessionView(props: SessionViewProps) {
const state = props.updateStatus?.state;
if (state === "ready") {
return props.anyActiveRuns
- ? `Update ready ${version}. Stop active runs to restart.`
- : `Restart to apply update ${version}`;
+ ? t("session.update_ready_stop_runs_title", undefined, { version })
+ : t("session.restart_update_title", undefined, { version });
}
- if (state === "downloading") return `Downloading update ${version}`;
- return `Update available ${version}`;
+ if (state === "downloading") return t("session.downloading_update_title", undefined, { version });
+ return t("session.update_available_title", undefined, { version });
});
const handleUpdatePillClick = () => {
@@ -2795,7 +2795,7 @@ export default function SessionView(props: SessionViewProps) {
const openProviderAuth = (preferredProviderId?: string) => {
void props.openProviderAuthModal({ preferredProviderId }).catch((error) => {
- const message = error instanceof Error ? error.message : "Connect failed";
+ const message = error instanceof Error ? error.message : t("session.connect_failed");
showStatusToast(message, "error");
});
};
@@ -2980,8 +2980,8 @@ export default function SessionView(props: SessionViewProps) {
@@ -3028,7 +3028,7 @@ export default function SessionView(props: SessionViewProps) {
{sessionHeaderTitle()}
- {props.selectedWorkspaceDisplay.displayName || props.selectedWorkspaceDisplay.name || "Workspace"}
+ {props.selectedWorkspaceDisplay.displayName || props.selectedWorkspaceDisplay.name || t("session.workspace_fallback")}
@@ -3059,11 +3059,11 @@ export default function SessionView(props: SessionViewProps) {
}
window.setTimeout(() => openCommandPalette(), 0);
}}
- title="Quick actions (Ctrl/Cmd+K)"
- aria-label="Quick actions"
+ title={t("session.quick_actions_title")}
+ aria-label={t("session.quick_actions_label")}
>
- Menu
+ {t("session.menu_label")}
⌘K
@@ -3082,8 +3082,8 @@ export default function SessionView(props: SessionViewProps) {
}
openSearch();
}}
- title="Search conversation (Ctrl/Cmd+F)"
- aria-label="Search conversation"
+ title={t("session.search_conversation_title")}
+ aria-label={t("session.search_conversation_label")}
>
@@ -3093,8 +3093,8 @@ export default function SessionView(props: SessionViewProps) {
class="flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-[13px] font-medium text-gray-10 transition-colors hover:bg-gray-2/70 hover:text-dls-text disabled:cursor-not-allowed disabled:opacity-60"
onClick={undoLastMessage}
disabled={!canUndoLastMessage() || historyActionBusy() !== null}
- title="Undo last message"
- aria-label="Undo last message"
+ title={t("session.undo_title")}
+ aria-label={t("session.undo_label")}
>
- Revert
+ {t("session.revert_label")}
- Redo
+ {t("session.redo_label")}
@@ -3147,8 +3147,8 @@ export default function SessionView(props: SessionViewProps) {
}
}}
class="min-w-0 flex-1 bg-transparent text-sm text-gray-11 placeholder:text-gray-9 focus:outline-none"
- placeholder="Search in this chat"
- aria-label="Search in this chat"
+ placeholder={t("session.search_placeholder")}
+ aria-label={t("session.search_placeholder")}
/>
{activeSearchPositionLabel()}
@@ -3158,24 +3158,24 @@ export default function SessionView(props: SessionViewProps) {
class="rounded-md border border-dls-border px-2 py-1 text-[11px] text-gray-10 transition-colors hover:bg-gray-2 hover:text-gray-12 disabled:opacity-60"
disabled={searchHits().length === 0}
onClick={() => moveSearchHit(-1)}
- aria-label="Previous match"
+ aria-label={t("session.prev_match")}
>
- Prev
+ {t("session.search_prev")}
moveSearchHit(1)}
- aria-label="Next match"
+ aria-label={t("session.next_match")}
>
- Next
+ {t("session.search_next")}
@@ -3223,9 +3223,9 @@ export default function SessionView(props: SessionViewProps) {
-
Loading session
+
{t("session.loading_title")}
- Pulling in the latest messages for this task.
+ {t("session.loading_detail")}
@@ -3296,10 +3296,10 @@ export default function SessionView(props: SessionViewProps) {
disabled={props.loadingEarlierMessages}
>
{props.loadingEarlierMessages
- ? "Loading earlier messages..."
+ ? t("session.loading_earlier")
: hiddenMessageCount() > 0
- ? `Show ${nextRevealCount().toLocaleString()} earlier message${nextRevealCount() === 1 ? "" : "s"}`
- : "Load earlier messages"}
+ ? t("session.show_earlier", undefined, { count: nextRevealCount().toLocaleString(), plural: nextRevealCount() === 1 ? "" : "s" })
+ : t("session.load_earlier")}
@@ -3375,7 +3375,7 @@ export default function SessionView(props: SessionViewProps) {
sessionScroll.jumpToStartOfMessage("smooth");
}}
>
- Jump to start of message
+ {t("session.jump_to_start")}
@@ -3387,7 +3387,7 @@ export default function SessionView(props: SessionViewProps) {
sessionScroll.jumpToLatest("smooth");
}}
>
- Jump to latest
+ {t("session.jump_to_latest")}
@@ -3476,7 +3476,7 @@ export default function SessionView(props: SessionViewProps) {
onSend={handleSendPrompt}
onStop={cancelRun}
onDraftChange={handleDraftChange}
- selectedModelLabel={modelControls.selectedSessionModelLabel() || "Model"}
+ selectedModelLabel={modelControls.selectedSessionModelLabel() || t("session.model_fallback")}
onModelClick={() => modelControls.openSessionModelPicker()}
modelVariantLabel={modelControls.sessionModelVariantLabel()}
modelVariant={modelControls.sessionModelVariant()}
@@ -3553,7 +3553,7 @@ export default function SessionView(props: SessionViewProps) {
class="h-8 px-2 rounded-md text-xs text-dls-secondary hover:text-dls-text hover:bg-dls-hover transition-colors"
onClick={returnToCommandRoot}
>
- Back
+ {t("session.back")}
@@ -3572,7 +3572,7 @@ export default function SessionView(props: SessionViewProps) {
type="button"
class="h-8 w-8 flex items-center justify-center rounded-md text-dls-secondary hover:text-dls-text hover:bg-dls-hover transition-colors"
onClick={closeCommandPalette}
- aria-label="Close quick actions"
+ aria-label={t("session.close_quick_actions")}
>
@@ -3587,7 +3587,7 @@ export default function SessionView(props: SessionViewProps) {
when={commandPaletteItems().length > 0}
fallback={
- No matches.
+ {t("session.no_matches_command")}
}
>
@@ -3633,8 +3633,8 @@ export default function SessionView(props: SessionViewProps) {
- Arrow keys to navigate
- Enter to run · Esc to close
+ {t("session.palette_hint_navigate")}
+ {t("session.palette_hint_run")}
@@ -3669,14 +3669,14 @@ export default function SessionView(props: SessionViewProps) {
- Permission
+ {t("session.permission_label")}
{activePermissionPresentation().permissionLabel}
@@ -3786,7 +3786,7 @@ export default function SessionView(props: SessionViewProps) {
>
- Details
+ {t("session.details_label")}
{props.safeStringify(props.activePermission?.metadata)}
@@ -3805,7 +3805,7 @@ export default function SessionView(props: SessionViewProps) {
}
disabled={props.permissionReplyBusy}
>
- Deny
+ {t("session.deny")}
- Once
+ {t("session.allow_once")}
- Allow for session
+ {t("session.allow_for_session")}
diff --git a/apps/app/src/app/session/share-workspace.ts b/apps/app/src/app/session/share-workspace.ts
index 21319ce80..c8ac73f70 100644
--- a/apps/app/src/app/session/share-workspace.ts
+++ b/apps/app/src/app/session/share-workspace.ts
@@ -7,7 +7,6 @@ import {
type Accessor,
} from "solid-js";
-import { t } from "../../i18n";
import {
publishSkillsSetBundleFromWorkspace,
@@ -29,6 +28,7 @@ import type {
WorkspaceInfo,
} from "../lib/tauri";
import type { OpenworkServerSettings } from "../lib/openwork-server";
+import { t } from "../../i18n";
import { isTauriRuntime, normalizeDirectoryPath } from "../utils";
export type ShareWorkspaceState = ReturnType;
@@ -199,28 +199,28 @@ export function createShareWorkspaceState(options: ShareWorkspaceStateOptions) {
label: t("session.share_worker_url"),
value: url,
placeholder: !isTauriRuntime()
- ? t("session.share_desktop_required")
+ ? t("session.share_desktop_app_required")
: t("session.share_starting_server"),
hint: mountedUrl
- ? t("session.share_url_worker_hint")
+ ? t("session.share_worker_url_phones_hint")
: hostUrl
- ? t("session.share_url_resolving_hint")
+ ? t("session.share_worker_url_resolving_hint")
: undefined,
},
{
label: t("session.share_password"),
value: ownerToken,
secret: true,
- placeholder: isTauriRuntime() ? "-" : t("session.share_desktop_required"),
+ placeholder: isTauriRuntime() ? "-" : t("session.share_desktop_app_required"),
hint: mountedUrl
- ? t("session.share_url_worker_hint")
- : t("session.share_password_owner_hint"),
+ ? t("session.share_worker_url_phones_hint")
+ : t("session.share_owner_permission_hint"),
},
{
- label: t("session.share_collaborator_token"),
+ label: t("session.share_collaborator_label"),
value: collaboratorToken,
secret: true,
- placeholder: isTauriRuntime() ? "-" : t("session.share_desktop_required"),
+ placeholder: isTauriRuntime() ? "-" : t("session.share_desktop_app_required"),
hint: mountedUrl
? t("session.share_collaborator_hint")
: t("session.share_collaborator_host_hint"),
diff --git a/apps/app/src/i18n/locales/en.ts b/apps/app/src/i18n/locales/en.ts
index 000717ce6..474cdeb40 100644
--- a/apps/app/src/i18n/locales/en.ts
+++ b/apps/app/src/i18n/locales/en.ts
@@ -197,6 +197,19 @@ export default {
"session.rename_description": "Update the name for this session.",
"session.rename_label": "Session name",
"session.rename_placeholder": "Enter a new name",
+ "session.provider_connected": "Provider connected",
+ "session.oauth_failed": "OAuth failed",
+ "session.api_key_saved": "API key saved",
+ "session.failed_to_save_api_key": "Failed to save API key",
+ "session.quick_actions_title": "Quick actions (Ctrl/Cmd+K)",
+ "session.quick_actions_label": "Quick actions",
+ "session.menu_label": "Menu",
+ "session.search_conversation_title": "Search conversation (Ctrl/Cmd+F)",
+ "session.search_conversation_label": "Search conversation",
+ "session.undo_title": "Undo last message",
+ "session.revert_label": "Revert",
+ "session.redo_title": "Redo last reverted message",
+ "session.redo_aria_label": "Redo last reverted message",
// ==================== Commands ====================
"commands.new": "New",
"commands.empty_state": "Save a prompt or command so you can run it again in one tap.",
@@ -966,6 +979,145 @@ export default {
"app.error_connection_failed_url": "Connection failed. Check the URL and token.",
"app.error_connection_failed": "Connection failed",
+ // ==================== Status bar labels ====================
+ "status.openwork_ready": "OpenWork Ready",
+ "status.limited_mode": "Limited Mode",
+ "status.disconnected_label": "Disconnected",
+ "status.ready_for_tasks": "Ready for new tasks",
+ "status.developer_mode": "Developer mode",
+ "status.providers_connected": "{count} provider{plural} connected",
+ "status.mcp_connected": "{count} MCP connected",
+ "status.limited_hint": "Reconnect to restore full OpenWork features",
+ "status.limited_mcp_hint": "{count} MCP connected · reconnect for full features",
+ "status.disconnected_hint": "Open settings to reconnect",
+ "status.back": "Back to previous screen",
+ "status.feedback": "Feedback",
+ "status.send_feedback": "Send feedback",
+ "status.settings": "Settings",
+
+ // ==================== Message List ====================
+ "message.waiting_subagent": "Waiting for the subagent transcript to arrive.",
+ "message.tool_request_label": "Request",
+ "message.tool_result_label": "Result",
+
+ // ==================== Message List (additional) ====================
+ "message_list.copy_message": "Copy message",
+
+ // ==================== Composer ====================
+ "composer.no_matches": "No matches found.",
+ "composer.loading_agents": "Loading agents...",
+ "composer.run_task": "Run task",
+ "composer.stop": "Stop",
+ "composer.agent_label": "Agent",
+ "composer.default_agent": "Default agent",
+ "composer.loading_commands": "Loading commands...",
+ "composer.no_commands": "No commands found.",
+ "composer.skill_source": "Skill",
+ "composer.attach_files": "Attach files",
+ "composer.image_kind": "Image",
+ "composer.file_kind": "File",
+ "composer.expand_pasted": "Click to expand pasted text",
+ "composer.failed_read_attachment": "Failed to read attachment",
+ "composer.attachments_unavailable": "Attachments are unavailable.",
+ "composer.file_exceeds_limit": "{name} exceeds the 8MB limit.",
+ "composer.file_too_large_encoding": "{name} is too large after encoding. Try a smaller image.",
+ "composer.uploaded_single_file": "Uploaded {name} to the shared folder and inserted a link.",
+ "composer.uploaded_multiple_files": "Uploaded {count} files to the shared folder and inserted links.",
+ "composer.upload_failed_local_links": "Couldn't upload to the shared folder. Inserted local links instead.",
+ "composer.unsupported_attachment_type": "Unsupported attachment type.",
+ "composer.inserted_links_unsupported": "Inserted links for unsupported files.",
+ "composer.remote_worker_paste_warning": "This is a remote worker. Sandboxes are remote too. To share files with it, upload them to the Shared folder in the sidebar.",
+ "composer.upload_to_shared_folder": "Upload to shared folder",
+ "composer.placeholder": "Describe your task...",
+ "composer.behavior_label": "Behavior",
+
+ // ==================== Context Panel ====================
+ "context_panel.context": "Context",
+ "context_panel.working_files": "Working files",
+ "context_panel.plugins": "Plugins",
+ "context_panel.no_plugins": "No plugins loaded.",
+ "context_panel.mcp": "MCP",
+ "context_panel.no_mcp": "No MCP servers loaded.",
+ "context_panel.skills": "Skills",
+ "context_panel.no_skills": "No skills loaded.",
+ "context_panel.authorized_folders": "Authorized folders",
+ "context_panel.none_yet": "None yet.",
+ "context_panel.mcp_disabled": "Disabled",
+ "context_panel.mcp_disconnected": "Disconnected",
+ "context_panel.mcp_connected": "Connected",
+ "context_panel.mcp_needs_auth": "Needs auth",
+ "context_panel.mcp_register_client": "Register client",
+ "context_panel.mcp_failed": "Failed",
+ "context_panel.open_file": "Open {path}",
+
+ // ==================== Inbox Panel ====================
+ "inbox_panel.shared_folder": "Shared folder",
+ "inbox_panel.refresh_tooltip": "Refresh shared folder",
+ "inbox_panel.uploading": "Uploading...",
+ "inbox_panel.upload_prompt": "Drop files or click to upload",
+ "inbox_panel.helper_text": "Share files with this worker from the app.",
+ "inbox_panel.connect_to_see": "Connect to see shared files.",
+ "inbox_panel.no_files": "No shared files yet.",
+ "inbox_panel.showing_first": "Showing first {count}.",
+ "inbox_panel.upload_needs_worker": "Connect to a worker to upload files to the shared folder.",
+ "inbox_panel.uploading_label": "Uploading {label}...",
+ "inbox_panel.upload_success": "Uploaded to the shared folder.",
+ "inbox_panel.upload_failed": "Shared folder upload failed",
+ "inbox_panel.copy_failed": "Copy failed. Your browser may block clipboard access.",
+ "inbox_panel.connect_to_download": "Connect to a worker to download shared files.",
+ "inbox_panel.missing_file_id": "Missing shared file id.",
+ "inbox_panel.download": "Download",
+ "inbox_panel.load_failed": "Failed to load shared folder",
+ "inbox_panel.drop_to_upload": "Drop files here to upload",
+ "inbox_panel.connect_to_upload": "Connect to a worker to upload",
+
+ // ==================== Session Sidebar ====================
+ "sidebar.no_workspaces": "No workspaces in this session yet. Add one to get started.",
+ "sidebar.needs_attention": "Needs attention",
+ "sidebar.active": "Active",
+ "sidebar.switch": "Switch",
+ "sidebar.expand": "Expand",
+ "sidebar.collapse": "Collapse",
+ "sidebar.drag_reorder": "Drag to reorder",
+ "sidebar.edit_connection": "Edit connection",
+ "sidebar.test_connection": "Test connection",
+ "sidebar.stop_sandbox": "Stop sandbox",
+ "sidebar.remove_workspace": "Remove workspace",
+ "sidebar.show_fewer": "Show fewer",
+ "sidebar.show_more": "Show {count} more",
+ "sidebar.add_workspace": "Add new workspace",
+ "sidebar.new_worker": "New worker",
+ "sidebar.connect_remote": "Connect remote",
+ "sidebar.import_config": "Import config",
+ "sidebar.progress": "Progress",
+ "sidebar.delete_session": "Delete session",
+ "sidebar.no_sessions_yet": "No sessions yet",
+
+ // ==================== Workspace Session List ====================
+ "workspace_list.connecting": "Connecting...",
+ "workspace_list.workspace_fallback": "Workspace",
+ "workspace_list.reveal_explorer": "Reveal in Explorer",
+ "workspace_list.reveal_finder": "Reveal in Finder",
+ "workspace_list.show_more": "Show {count} more",
+ "workspace_list.show_more_fallback": "Show more",
+ "workspace_list.workspace_options": "Workspace options",
+ "workspace_list.session_actions": "Session actions",
+ "workspace_list.hide_child_sessions": "Hide child sessions",
+ "workspace_list.show_child_sessions": "Show child sessions",
+ "workspace_list.edit_name": "Edit name",
+ "workspace_list.share": "Share...",
+ "workspace_list.recover": "Recover",
+ "workspace_list.test_connection": "Test connection",
+ "workspace_list.edit_connection": "Edit connection",
+ "workspace_list.remove_workspace": "Remove workspace",
+ "workspace_list.rename_session": "Rename session",
+ "workspace_list.delete_session": "Delete session",
+ "workspace_list.add_workspace": "Add workspace",
+ "workspace_list.new_workspace": "New workspace",
+ "workspace_list.connect_remote": "Connect remote workspace",
+ "workspace_list.import_config": "Import config",
+ "workspace_list.desktop_only_hint": "Create local workspaces in the desktop app.",
+
// ==================== Workspace badges ====================
"workspace.sandbox_badge": "Sandbox",
"workspace.remote_badge": "Remote",
@@ -1222,6 +1374,7 @@ export default {
// ==================== Dashboard page (session/share strings) ====================
"scheduled.title": "Automations",
+ "extensions.title": "Extensions",
"settings.tab_automations": "Automations",
"settings.tab_skills": "Skills",
"settings.tab_extensions": "Extensions",
@@ -1242,6 +1395,131 @@ export default {
"session.share_password": "Password",
"session.share_password_owner_hint": "Use when the remote client must answer permission prompts.",
"session.share_collaborator_token": "Collaborator token",
+
+ // ==================== Workspace (additions) ====================
+ "workspace.needs_attention": "Needs attention",
+ "workspace.active": "Active",
+ "workspace.switch": "Switch",
+ "workspace.no_tasks": "No tasks yet.",
+ "workspace.new_task_inline": "+ New task",
+ "workspace.loading_tasks": "Loading tasks...",
+ "workspace.selected": "Selected",
+
+ // ==================== Common (additions) ====================
+ "common.copy": "Copy",
+ "common.copied": "Copied",
+ "common.reset": "Reset",
+ "common.add": "Add",
+ "common.done": "Done",
+
+ // ==================== Session view ====================
+ "session.permission_message": "OpenCode is requesting permission to continue.",
+ "session.doom_loop_title": "Doom Loop Detected",
+ "session.doom_loop_message": "OpenCode detected repeated tool calls with identical input and is asking whether it should continue after repeated failures.",
+ "session.doom_loop_label": "Doom Loop",
+ "session.doom_loop_tool_label": "Tool",
+ "session.doom_loop_repeated_call_label": "Repeated call",
+ "session.doom_loop_repeated_tool_call": "Repeated tool call",
+ "session.doom_loop_note": "Reject to stop the loop, or allow if you want the agent to keep trying.",
+ "session.default_agent": "Default agent",
+ "session.workspace_label": "Workspace",
+ "session.no_matches": "No matches",
+ "session.status_delegating": "Delegating",
+ "session.status_planning": "Planning",
+ "session.status_gathering_context": "Gathering context",
+ "session.status_searching_codebase": "Searching codebase",
+ "session.status_searching_web": "Searching the web",
+ "session.status_writing_file": "Writing file",
+ "session.status_running_shell": "Running shell",
+ "session.status_working": "Working",
+ "session.status_thinking": "Thinking",
+ "session.phase_sending": "Sending",
+ "session.phase_retrying": "Retrying",
+ "session.phase_responding": "Responding",
+ "session.phase_run_failed": "Run failed",
+ "session.compacting_auto": "OpenCode is auto-compacting this session",
+ "session.compacting_manual": "OpenCode is compacting this session",
+ "session.compaction_started": "OpenCode started compacting the session context.",
+ "session.compaction_finished": "OpenCode finished compacting the session context.",
+ "session.no_session_selected": "No session selected",
+ "session.stopping_run": "Stopping the run...",
+ "session.stopped": "Stopped.",
+ "session.failed_to_stop": "Failed to stop",
+ "session.nothing_to_retry": "Nothing to retry yet",
+ "session.trying_again": "Trying again...",
+ "session.nothing_to_undo": "Nothing to undo yet.",
+ "session.reverted_last_message": "Reverted the last user message.",
+ "session.failed_to_undo": "Failed to undo",
+ "session.nothing_to_redo": "Nothing to redo.",
+ "session.restored_message": "Restored the reverted message.",
+ "session.failed_to_redo": "Failed to redo",
+ "session.nothing_to_compact": "Nothing to compact yet.",
+ "session.compacting": "Compacting session context...",
+ "session.compacted": "Session compacted.",
+ "session.failed_to_compact": "Failed to compact session",
+ "session.deleted": "Session deleted",
+ "session.failed_to_delete": "Failed to delete session",
+ "session.failed_to_load_agents": "Failed to load agents",
+ "session.failed_to_create_session": "Failed to create session",
+ "session.failed_to_load_providers": "Failed to load providers",
+ "session.cmd_new_session_title": "Create new session",
+ "session.cmd_new_session_detail": "Start a fresh task in the current workspace",
+ "session.cmd_new_session_meta": "Create",
+ "session.cmd_rename_title": "Rename current session",
+ "session.cmd_rename_detail_fallback": "Give your selected session a clearer name",
+ "session.cmd_rename_meta": "Rename",
+ "session.cmd_compact_title": "Compact Conversation",
+ "session.cmd_compact_detail": "Send a compact instruction to OpenCode for this session",
+ "session.cmd_compact_detail_empty": "No user messages to compact yet",
+ "session.cmd_compact_meta": "Compact",
+ "session.cmd_sessions_title": "Search sessions",
+ "session.cmd_sessions_meta": "Jump",
+ "session.cmd_model_title": "Change model",
+ "session.cmd_model_meta": "Open",
+ "session.cmd_model_fallback": "Model",
+ "session.cmd_provider_title": "Connect provider",
+ "session.cmd_provider_detail": "Open provider connection flow",
+ "session.cmd_provider_meta": "Open",
+ "session.cmd_current_workspace": "Current workspace",
+ "session.cmd_switch": "Switch",
+ "session.palette_title_sessions": "Search sessions",
+ "session.palette_title_actions": "Quick actions",
+ "session.palette_placeholder_sessions": "Find by session title or workspace",
+ "session.palette_placeholder_actions": "Search actions",
+ "session.search_placeholder": "Search in this chat",
+ "session.search_prev": "Prev",
+ "session.search_next": "Next",
+ "session.undo_label": "Revert",
+ "session.redo_label": "Redo",
+ "session.loading_title": "Loading session",
+ "session.loading_detail": "Pulling in the latest messages for this task.",
+ "session.workspace_setup_label": "Workspace setup",
+ "session.workspace_setup_title": "Set up your first workspace",
+ "session.workspace_setup_desc": "Start with a guided OpenWork workspace, or choose an existing folder you want to work in.",
+ "session.create_workspace_title": "Create workspace",
+ "session.create_workspace_desc": "Open the workspace creator and choose how you want to start.",
+ "session.pick_folder_title": "Pick a folder you want to work in",
+ "session.pick_folder_desc": "Choose an existing project or notes folder and OpenWork will use it as your workspace.",
+ "session.loading_earlier": "Loading earlier messages...",
+ "session.load_earlier": "Load earlier messages",
+ "session.jump_to_start": "Jump to start of message",
+ "session.jump_to_latest": "Jump to latest",
+ "session.flyout_new_task": "New Task",
+ "session.flyout_file_modified": "File Modified",
+ "session.attachments_add_token": "Add a server token to attach files.",
+ "session.attachments_connect_server": "Connect to OpenWork server to attach files.",
+ "session.unable_to_reveal": "Unable to reveal workspace",
+ "session.workspace_path_unavailable": "Workspace path is unavailable.",
+ "session.reveal_desktop_only": "Reveal is available in the desktop app.",
+ "session.file_open_remote_unavailable": "File open is unavailable for remote workspaces.",
+ "session.file_open_desktop_only": "File open is available in the desktop app.",
+ "session.pick_workspace_to_open": "Pick a workspace to open files.",
+ "session.unable_to_open_file": "Unable to open file",
+ "session.share_collaborator_label": "Collaborator token",
+ "session.share_desktop_app_required": "Desktop app required",
+ "session.share_worker_url_phones_hint": "Use on phones or laptops connecting to this worker.",
+ "session.share_worker_url_resolving_hint": "Worker URL is resolving; host URL shown as fallback.",
+ "session.share_owner_permission_hint": "Use when the remote client must answer permission prompts.",
"session.share_collaborator_hint": "Routine remote access when you do not need owner-only actions.",
"session.share_collaborator_host_hint": "Routine remote access to this host without owner-only actions.",
"session.share_set_token_hint": "Set token in workspace settings",
@@ -1275,6 +1553,13 @@ export default {
"session.install_update": "Install update",
"session.downloading": "Downloading",
"session.update_available": "Update available",
+ "session.connect_failed": "Connect failed",
+ "session.upload_connect_server": "Connect to the OpenWork server to upload files to the shared folder.",
+ "session.uploaded_to_shared_folder": "Uploaded to the shared folder.",
+ "session.create_or_connect_workspace": "Create or connect a workspace",
+ "session.allow_once": "Once",
+ "session.connect_to_sync": "Connect to OpenWork server to sync remote files.",
+ "session.obsidian_worker_relative_only": "Only worker-relative files can be opened in Obsidian.",
"session.resize_workspace_column": "Resize workspace column",
"dashboard.close_settings": "Close settings",
"dashboard.nav_ids": "IDs",
@@ -1283,421 +1568,71 @@ export default {
"session.restart_update_title": "Restart to apply update {version}",
"session.downloading_update_title": "Downloading update {version}",
"session.update_available_title": "Update available {version}",
+ "session.file_open_failed": "File open failed",
+ "session.remote_sync_failed": "Remote file sync failed",
+ "session.shared_folder_upload_failed": "Shared folder upload failed",
+ "session.status_compacting": "Compacting Context",
+ "session.status_active": "Session Active",
+ "session.status_ready_session": "Session Ready",
+ "session.status_ready": "Ready",
+ "session.delete_session_title": "Delete session?",
+ "session.delete_named_session_message": "This will permanently delete \"{title}\" and its messages.",
+ "session.delete_session_generic": "This will permanently delete the selected session and its messages.",
+ "session.deleting": "Deleting...",
+ "session.delete": "Delete",
+ "session.loading_session": "Loading session",
+ "session.workspace_fallback": "Workspace",
+ "session.model_fallback": "Model",
+ "session.close_quick_actions": "Close quick actions",
+ "session.prev_match": "Previous match",
+ "session.next_match": "Next match",
+ "session.close_search": "Close search",
+ "session.unable_to_open_obsidian": "Unable to open file in Obsidian",
+ "session.search_position": "{current} of {total}",
+ "session.conflict_sync_toast": "Conflict syncing {path}. Saved local changes to {conflictPath}.",
+ "session.todo_progress": "{completed} out of {total} tasks completed",
+ "session.uploading_to_shared_folder": "Uploading {label} to the shared folder...",
+ "session.uploaded_with_summary": "Uploaded to the shared folder: {summary}",
+ "session.cmd_sessions_detail": "{count} available across workspaces",
+ "session.cmd_model_detail": "{model} · {variant}",
+ "session.show_earlier": "Show {count} earlier message{plural}",
+ "session.back": "Back",
+ "session.no_matches_command": "No matches.",
+ "session.palette_hint_navigate": "Arrow keys to navigate",
+ "session.palette_hint_run": "Enter to run · Esc to close",
- // ==================== Common (PR3) ====================
- "common.reset": "Reset",
-
- // ==================== Config (PR3) ====================
- "config.auto_reload_desc": "Reload automatically after agents/skills/commands/config change (only when idle).",
- "config.auto_reload_title": "Auto reload (local)",
- "config.auto_reload_unavailable": "Available for local workspaces in the desktop app.",
- "config.collaborator_token_disabled_hint": "Stored in advance for remote sharing, but remote access is currently disabled.",
- "config.collaborator_token_label": "Collaborator token",
- "config.collaborator_token_remote_hint": "Routine remote access for phones or laptops connecting to this server.",
- "config.connection_failed": "Connection failed.",
- "config.connection_failed_check": "Connection failed. Check the host URL and token.",
- "config.connection_status_updated": "Connection status updated.",
- "config.connection_successful": "Connection successful.",
- "config.copied": "Copied",
- "config.copy": "Copy",
- "config.desktop_only_hint": "Some config features (local server sharing + messaging bridge) require the desktop app.",
- "config.diagnostics_desc": "Copy sanitized runtime state for debugging.",
- "config.diagnostics_title": "Diagnostics bundle",
- "config.enable_auto_reload_first": "Enable auto reload first",
- "config.engine_reload_desc": "Restart the OpenCode server for this workspace.",
- "config.engine_reload_title": "Engine reload",
- "config.host_admin_token_hint": "Internal host-only token for approvals CLI and admin APIs. Do not use this in the remote app connect flow.",
- "config.host_admin_token_label": "Host admin token",
- "config.host_local_only": "Local only",
- "config.host_offline": "Offline",
- "config.host_remote_enabled": "Remote enabled",
- "config.local_ip_hint": "Use your local IP on the same Wi-Fi for the fastest connection.",
- "config.mdns_hint": ".local names are easier to remember but may not resolve on all networks.",
- "config.messaging_identities_desc": "Manage Telegram/Slack identities and routing in the Identities tab.",
- "config.messaging_identities_title": "Messaging identities",
- "config.not_set": "Not set",
- "config.owner_token_disabled_hint": "Only relevant after you enable remote access for this worker.",
- "config.owner_token_label": "Owner token",
- "config.owner_token_remote_hint": "Use this when a remote client needs to answer permission prompts or take owner-only actions.",
- "config.reload_active_tasks_warning": "Reloading will stop active tasks.",
- "config.reload_availability_hint": "Reloading is only available for local workers or connected OpenWork servers.",
- "config.reload_connect_hint": "Connect to this worker to reload.",
- "config.reload_engine": "Reload engine",
- "config.reload_now_desc": "Applies config updates and reconnects your session.",
- "config.reload_now_title": "Reload now",
- "config.reloading": "Reloading...",
- "config.remote_access_off_hint": "Remote access is off. Use Share workspace to enable it before connecting from another machine.",
- "config.resolved_worker_url": "Resolved worker URL:",
- "config.resume_sessions_desc": "If a reload was queued while tasks were running, send a resume message afterward.",
- "config.resume_sessions_title": "Resume sessions after auto reload",
- "config.server_needed_hint": "OpenWork server connection needed to sync skills, plugins, and commands.",
- "config.server_section_desc": "Connect to an OpenWork server. Use the URL plus a collaborator or owner token from your server admin.",
- "config.server_section_title": "OpenWork server",
- "config.server_sharing_desc": "Share these details with a trusted device. Keep the server on the same network for the fastest setup.",
- "config.server_sharing_menu_hint": "For per-workspace sharing links, use Share... in the workspace menu.",
- "config.server_sharing_title": "OpenWork server sharing",
- "config.server_url_hint": "Use the URL shared by your OpenWork server. Local desktop workers reuse a persistent high port in the 48000-51000 range.",
- "config.server_url_input_label": "OpenWork server URL",
- "config.server_url_label": "OpenWork Server URL",
- "config.starting_server": "Starting server\\u2026",
- "config.status_connected": "Connected",
- "config.status_limited": "Limited",
- "config.status_not_connected": "Not connected",
- "config.test_connection": "Test connection",
- "config.testing": "Testing...",
- "config.testing_connection": "Testing connection...",
- "config.token_hint": "Optional. Paste a collaborator token for routine access or an owner token when this client must answer permission prompts.",
- "config.token_label": "Collaborator or owner token",
- "config.token_placeholder": "Paste your token",
- "config.unavailable": "Unavailable",
- "config.worker_id": "Worker ID:",
- "config.workspace_config_desc": "These settings affect the selected workspace. Runtime-only actions apply to whichever workspace is currently connected.",
- "config.workspace_config_title": "Workspace config",
- "config.workspace_id_prefix": "Workspace:",
-
- // ==================== Extensions (PR3) ====================
- "extensions.app_count_many": "apps connected",
- "extensions.app_count_one": "app connected",
- "extensions.apps_mcp_header": "Apps (MCP)",
- "extensions.filter_all": "All",
- "extensions.filter_apps": "Apps",
- "extensions.filter_plugins": "Plugins",
- "extensions.plugin_count_many": "plugins",
- "extensions.plugin_count_one": "plugin",
- "extensions.plugins_opencode_header": "Plugins (OpenCode)",
- "extensions.subtitle": "Apps (MCP) and OpenCode plugins live in one place.",
- "extensions.title": "Extensions",
-
- // ==================== Identities (PR3) ====================
- "identities.agent_behavior_desc": "One file per workspace. Add optional first line @agent to route via a specific OpenCode agent.",
- "identities.agent_behavior_title": "Messaging agent behavior",
- "identities.agent_created": "Created default messaging agent file.",
- "identities.agent_file_changed": "File changed remotely. Reload and save again.",
- "identities.agent_loading": "Loading agent file\\u2026",
- "identities.agent_not_found": "Agent file not found in this workspace yet.",
- "identities.agent_placeholder": "Add messaging behavior instructions for opencodeRouter here...",
- "identities.agent_saved": "Saved messaging behavior.",
- "identities.agent_worker_scope_unavailable": "Worker scope unavailable.",
- "identities.all_channels": "All channels",
- "identities.app_token_label": "App token",
- "identities.auto_bind_label": "Auto-bind peer to directory on direct send",
- "identities.available_channels": "Available channels",
- "identities.bot_token_label": "Bot token",
- "identities.channel_label": "Channel",
- "identities.channels_connected": "connected",
- "identities.channels_label": "Channels",
- "identities.configured_suffix": "configured",
- "identities.connect_server_desc": "Identities are available when you are connected to an OpenWork host.",
- "identities.connect_server_title": "Connect to an OpenWork server",
- "identities.connect_slack": "Connect Slack",
- "identities.connected_badge": "Connected",
- "identities.connecting": "Connecting...",
- "identities.copy_bot_token_hint": "Copy the bot token and paste it below.",
- "identities.copy_code": "Copy code",
- "identities.create_default_file": "Create default file",
- "identities.create_private_bot": "Create private bot",
- "identities.create_public_bot": "Create public bot",
- "identities.default_routing": "Default routing",
- "identities.directory_label": "Directory (optional)",
- "identities.disable_messaging": "Disable messaging",
- "identities.disable_messaging_message": "This will turn off messaging for this workspace. Telegram and Slack setup will be hidden until messaging is enabled again, and you will need to restart the worker to fully stop the messaging sidecar.",
- "identities.disable_messaging_title": "Disable messaging for this worker?",
- "identities.disabled_label": "Disabled",
- "identities.disabling": "Disabling...",
- "identities.disconnect": "Disconnect",
- "identities.enable_messaging": "Enable messaging",
- "identities.enable_messaging_risk": "Messaging can expose this worker to remote commands. If a bot is public or compromised, it can access files, credentials, and API keys available to this worker.",
- "identities.enable_messaging_title": "Enable messaging for this worker?",
- "identities.enabled_label": "Enabled",
- "identities.enabling": "Enabling...",
- "identities.health_offline": "Offline",
- "identities.health_running": "Running",
- "identities.health_unavailable": "Unavailable",
- "identities.health_unknown": "Unknown",
- "identities.identities_label": "Identities",
- "identities.just_now": "Just now",
- "identities.last_activity": "Last activity",
- "identities.later": "Later",
- "identities.message_label": "Message",
- "identities.message_routing_desc": "Control which conversations go to which workspace folder. Messages are routed to the worker's default folder unless you set up rules here.",
- "identities.message_routing_title": "Message routing",
- "identities.messages_today": "Messages today",
- "identities.messaging_disabled_hint": "Enable messaging only if you understand the risk and plan to secure access (for example, private Telegram pairing).",
- "identities.messaging_disabled_restart": "Messaging disabled. Restart this worker to stop the messaging sidecar.",
- "identities.messaging_disabled_risk": "Messaging bots can execute actions against your local worker. If exposed publicly, they may allow access to files, credentials, and API keys available to this worker.",
- "identities.messaging_disabled_title": "Messaging is disabled by default",
- "identities.messaging_enabled_restart": "Messaging enabled. Restart this worker to apply before configuring channels.",
- "identities.messaging_sidecar_not_running": "Messaging is enabled in this workspace, but the messaging sidecar is not running yet. Restart this worker, then return to Messaging settings to connect Telegram or Slack.",
- "identities.not_set": "Not set",
- "identities.pairing_code_copied": "Pairing code copied.",
- "identities.pairing_code_copy_failed": "Could not copy pairing code. Copy it manually.",
- "identities.peer_id_label": "Peer ID (optional)",
- "identities.private_label": "Private",
- "identities.private_pairing_code": "Private pairing code",
- "identities.public_bot_confirm": "Yes I understand the risk",
- "identities.public_bot_warning_message": "Your bot will be accessible to the public and anyone who gets access to your bot will be able to have full access to your local worker including any files or API keys that you've given it. If you create a private bot, you can limit who can access it by requiring a pairing token. Are you sure you want to make your bot public?",
- "identities.public_bot_warning_title": "Make this bot public?",
- "identities.public_label": "Public",
- "identities.quick_setup": "Quick setup",
- "identities.reconnect_failed": "Reconnect failed. Check OpenWork URL/token and try again.",
- "identities.reconnected": "Reconnected.",
- "identities.reconnected_refreshing": "Reconnected. Refreshing worker state...",
- "identities.reload": "Reload",
- "identities.repair_reconnect": "Repair & reconnect",
- "identities.restart_failed": "Restart failed. Please restart the worker from Settings and try again.",
- "identities.restart_to_disable_messaging": "Messaging was disabled for this workspace. Restart the worker now to stop the messaging sidecar.",
- "identities.restart_to_enable_messaging": "Messaging was enabled for this workspace. Restart the worker now to start the messaging sidecar and unlock Telegram and Slack setup.",
- "identities.restart_worker": "Restart worker",
- "identities.restart_worker_title": "Restart worker now?",
- "identities.restarting": "Restarting...",
- "identities.running_label": "Running",
- "identities.save_behavior": "Save behavior",
- "identities.saving": "Saving...",
- "identities.send_test_button": "Send test message",
- "identities.send_test_desc": "Validate outbound wiring. Use a peer ID for direct send, or leave peer ID empty to fan out by bindings in a directory.",
- "identities.send_test_title": "Send test message",
- "identities.sending": "Sending...",
- "identities.slack_desc": "Your worker appears as a bot in Slack channels. Team members can message it directly or mention it in threads.",
- "identities.slack_intro": "Connect your Slack workspace to let team members interact with this worker in channels and DMs.",
- "identities.slack_unavailable": "Slack identities unavailable.",
- "identities.status_active": "Active",
- "identities.status_label": "Status",
- "identities.status_stopped": "Stopped",
- "identities.stopped_label": "Stopped",
- "identities.subtitle": "Let people reach your worker through messaging apps. Connect a channel and your worker will automatically read and respond to messages.",
- "identities.tab_general": "General",
- "identities.telegram_bot_access_desc": "Public bot: first Telegram chat auto-links. Private bot: requires a pairing code before any messages run tools.",
- "identities.telegram_delete_failed": "Failed to delete.",
- "identities.telegram_deleted": "Deleted.",
- "identities.telegram_deleted_pending": "Deleted (pending apply).",
- "identities.telegram_desc": "Connect a Telegram bot in public mode (open inbox) or private mode (pairing code required).",
- "identities.telegram_save_failed": "Failed to save.",
- "identities.telegram_saved": "Saved.",
- "identities.telegram_saved_pending": "Saved (pending apply).",
- "identities.telegram_unavailable": "Telegram identities unavailable.",
- "identities.title": "Messaging channels",
- "identities.unsaved_changes": "Unsaved changes",
- "identities.worker_offline": "Worker offline",
- "identities.worker_online": "Worker online",
- "identities.worker_restarted": "Worker restarted.",
- "identities.worker_restarted_refreshing": "Worker restarted. Refreshing messaging status...",
- "identities.worker_scope_unavailable": "Worker scope unavailable.",
- "identities.worker_scope_unavailable_detail": "Worker scope unavailable. Reconnect using a worker URL or switch to a known worker.",
- "identities.worker_unavailable": "Worker unavailable",
- "identities.workspace_id_required": "Workspace ID is required to manage identities. Reconnect with a workspace URL or select a workspace mapped on this host.",
- "identities.workspace_scope_prefix": "Workspace scope:",
-
- // ==================== Plugins (PR3) ====================
- "plugins.desc": "Manage `opencode.json` for your project or global OpenCode plugins.",
- "plugins.empty": "No plugins configured yet.",
- "plugins.not_loaded_yet": "Not loaded yet",
- "plugins.remove": "Remove",
- "plugins.suggested_heading": "Suggested plugins",
-
- // ==================== Scheduled (PR3) ====================
- "scheduled.at_time": "At {time}",
- "scheduled.badge_end_of_day": "End-of-day",
- "scheduled.badge_every_few_hours": "Every few hours",
- "scheduled.badge_friday_wrapup": "Friday wrap-up",
- "scheduled.badge_weekday_evening": "Weekday evening",
- "scheduled.badge_weekday_morning": "Weekday morning",
- "scheduled.badge_weekend_review": "Weekend review",
- "scheduled.create_button": "Create",
- "scheduled.create_desc": "Automations are scheduled by running a prompt in a new thread. We'll prefill a prompt for you to send.",
- "scheduled.create_title": "Create automation",
- "scheduled.created_prefix": "Created",
- "scheduled.custom_schedule": "Custom schedule",
- "scheduled.daily_mode": "Daily",
- "scheduled.day_fri": "Fri",
- "scheduled.day_mon": "Mon",
- "scheduled.day_sat": "Sat",
- "scheduled.day_sun": "Sun",
- "scheduled.day_thu": "Thu",
- "scheduled.day_tue": "Tue",
- "scheduled.day_wed": "Wed",
- "scheduled.days_at": "{days} at {time}",
- "scheduled.default_automation_name": "Daily bug scan",
- "scheduled.delete_confirm_title": "Delete automation?",
- "scheduled.delete_error_fallback": "Failed to delete job.",
- "scheduled.delete_label": "Delete",
- "scheduled.deleting": "Deleting",
- "scheduled.desktop_required": "Scheduled tasks require the desktop app.",
- "scheduled.empty_hint": "No automations yet. Pick a template or create your own automation prompt.",
- "scheduled.every_day_at": "Every day at {time}",
- "scheduled.every_hour": "Every hour",
- "scheduled.every_n_hours": "Every {interval} hours",
- "scheduled.every_prefix": "Every",
- "scheduled.explore_more": "Explore more",
- "scheduled.failed_status": "Failed",
- "scheduled.filter_all": "All",
- "scheduled.filter_scheduled": "Scheduled",
- "scheduled.filter_templates": "Templates",
- "scheduled.hours_suffix": "hours",
- "scheduled.install_scheduler": "Install scheduler",
- "scheduled.install_scheduler_hint": "Automations run through the opencode-scheduler plugin. Add it to this workspace to enable scheduling.",
- "scheduled.install_scheduler_title": "Install the scheduler to unlock automations",
- "scheduled.installing": "Installing...",
- "scheduled.interval_mode": "Interval",
- "scheduled.last_run_prefix": "Last run",
- "scheduled.last_updated_prefix": "Last updated",
- "scheduled.name_label": "Name",
- "scheduled.never": "Never",
- "scheduled.new_automation": "New automation",
- "scheduled.no_automations_match": "No automations match this search.",
- "scheduled.no_templates_match": "No templates match this search.",
- "scheduled.not_run_yet": "Not run yet",
- "scheduled.not_synced_yet": "Not synced yet",
- "scheduled.page_description": "Schedule recurring tasks for this worker, monitor what is already registered, and start from a reusable template.",
- "scheduled.prepare_error_fallback": "Failed to prepare automation in chat.",
- "scheduled.quick_start_templates": "Quick start templates",
- "scheduled.quick_start_templates_desc": "Start from a proven recurring workflow, then tailor the prompt before you prepare it in chat.",
- "scheduled.refreshing": "Refreshing",
- "scheduled.reload_activate_hint": "OpenCode loads plugins at startup. Reload OpenWork to activate opencode-scheduler.",
- "scheduled.reload_activate_title": "Reload OpenWork to activate automations",
- "scheduled.reload_openwork": "Reload OpenWork",
- "scheduled.reloading": "Reloading...",
- "scheduled.run_label": "Run",
- "scheduled.running_status": "Running",
- "scheduled.schedule_label": "Schedule",
- "scheduled.search_placeholder": "Search automations or templates",
- "scheduled.source_local": "From local scheduler",
- "scheduled.source_remote": "From OpenWork server",
- "scheduled.subtitle_local": "Automations that run on a schedule from this device.",
- "scheduled.subtitle_remote": "Automations that run on a schedule from the connected OpenWork server.",
- "scheduled.success_status": "Success",
- "scheduled.task_summary_no_prompt": "No prompt or command found.",
- "scheduled.task_summary_prompt": "Prompt",
- "scheduled.template_badge": "Template",
- "scheduled.template_count": "{count} templates",
- "scheduled.tpl_daily_planning_desc": "Build a focused plan from your tasks and calendar.",
- "scheduled.tpl_daily_planning_name": "Daily planning brief",
- "scheduled.tpl_habit_checkin_desc": "Run a quick accountability check through the day.",
- "scheduled.tpl_habit_checkin_name": "Habit check-in",
- "scheduled.tpl_inbox_zero_desc": "Summarize unread messages and draft short replies.",
- "scheduled.tpl_inbox_zero_name": "Inbox zero helper",
- "scheduled.tpl_learning_digest_desc": "Turn saved links and notes into a weekly digest.",
- "scheduled.tpl_learning_digest_name": "Learning digest",
- "scheduled.tpl_meeting_prep_desc": "Generate prep bullets for tomorrow's meetings.",
- "scheduled.tpl_meeting_prep_name": "Meeting prep notes",
- "scheduled.tpl_weekly_wins_desc": "Create a Friday recap of wins, blockers, and next steps.",
- "scheduled.tpl_weekly_wins_name": "Weekly wins recap",
- "scheduled.view_scheduler_docs": "View scheduler docs",
- "scheduled.weekdays_at": "Weekdays at {time}",
- "scheduled.weekends_at": "Weekends at {time}",
- "scheduled.worker_root_hint": "Worker root is inferred from the selected workspace.",
- "scheduled.your_automations": "Your automations",
-
- // ==================== Settings (PR3) ====================
- "settings.cap_browser_tools": "Browser tools: {value}",
- "settings.cap_commands": "Commands: {value}",
- "settings.cap_config": "Config: {value}",
- "settings.cap_file_tools": "File tools: {value}",
- "settings.cap_mcp": "MCP: {value}",
- "settings.cap_plugins": "Plugins: {value}",
- "settings.cap_proxy": "Proxy (OpenCodeRouter): {value}",
- "settings.cap_sandbox": "Sandbox: {value}",
- "settings.cap_skills": "Skills: {value}",
- "settings.debug_commit": "Commit: {sha}",
- "settings.debug_desktop_app": "Desktop app: {version}",
- "settings.debug_opencode_router_version": "OpenCodeRouter: {version}",
- "settings.debug_opencode_version": "OpenCode: {version}",
- "settings.debug_openwork_server_version": "OpenWork server: {version}",
- "settings.debug_orchestrator_version": "Orchestrator: {version}",
- "settings.diag_approval": "Approval: {mode} ({ms}ms)",
- "settings.diag_config_path": "Config path: {path}",
- "settings.diag_daemon_url": "Daemon: {url}",
- "settings.diag_health_port": "Health port: {port}",
- "settings.diag_healthy_ms": "Healthy: {ms}ms",
- "settings.diag_host_token_source": "Host token source: {source}",
- "settings.diag_last_attempt": "Last attempt: {time}",
- "settings.diag_load_sessions_ms": "Load sessions: {ms}ms",
- "settings.diag_opencode_binary": "OpenCode binary: {binary}",
- "settings.diag_opencode_url": "OpenCode: {url}",
- "settings.diag_pending_permissions_ms": "Pending permissions: {ms}ms",
- "settings.diag_pid": "PID: {pid}",
- "settings.diag_providers_ms": "Providers: {ms}ms",
- "settings.diag_read_only": "Read-only: {value}",
- "settings.diag_reason": "Reason: {reason}",
- "settings.diag_runtime_workspace": "Runtime workspace: {id}",
- "settings.diag_selected_workspace": "Selected workspace: {id}",
- "settings.diag_sidecar": "Sidecar: {info}",
- "settings.diag_started": "Started: {time}",
- "settings.diag_token_source": "Token source: {source}",
- "settings.diag_total_ms": "Total: {ms}ms",
- "settings.diag_version": "Version: {version}",
- "settings.diag_workspaces": "Workspaces: {count}",
- "settings.downloading_bytes": "Downloading {downloaded}",
- "settings.downloading_progress": "Downloading {downloaded} / {total} ({percent}%)",
- "settings.messaging_section_desc": "Manage Telegram/Slack identities and bindings in the Identities tab.",
- "settings.messaging_section_title": "Messaging",
- "settings.sandbox_result": "Result: {status}",
- "settings.sandbox_run_id": "Run ID: {id}",
- "settings.update_available_version": "Update available: v{version}",
- "settings.update_last_checked": "Last checked {time}",
- "settings.update_published": "Published {date}",
- "settings.update_ready_version": "Ready to install: v{version}",
-
- // ==================== Skills (PR3) ====================
- "skills.from_repo": "From {owner}/{repo}",
- "skills.install_name_title": "Install {name}",
- "skills.shown_count": "{count} shown",
- "skills.trigger_label": "Trigger: {trigger}",
-
- // ==================== Second Pass Additions ====================
-
- // Identities (second pass)
- "identities.minutes_ago": "{minutes}m ago",
- "identities.hours_ago": "{hours}h ago",
- "identities.days_ago": "{days}d ago",
- "identities.dispatched_messages": "Dispatched {sent}/{attempted} messages.",
- "identities.health_unavailable_status": "OpenCodeRouter health unavailable ({status})",
- "identities.telegram_private_saved_pair": "Private bot saved. Pair via /pair {code}",
- "identities.telegram_saved_username": "Saved (@{username})",
- "identities.botfather_step1_open": "1. Open @BotFather in Telegram",
- "identities.botfather_step1_run": "and run /newbot",
- "identities.botfather_step3_choose": "3. Choose a name and username for your bot",
- "identities.botfather_step3_public": "Public",
- "identities.botfather_step3_or_private": "for open inbox or",
- "identities.botfather_step3_private": "Private",
- "identities.botfather_step3_to_require": "to require",
- "identities.botfather_step_public": "Public bots can be found by anyone.",
- "identities.botfather_step_or_private": "Or create a private bot:",
- "identities.botfather_step_private": "Private bots require an invite link to start.",
- "identities.botfather_step_to_require": "To require users to be added, enable the privacy mode via @BotFather.",
- "identities.bot_token_placeholder": "Paste Telegram bot token from @BotFather",
- "identities.pairing_code_instruction_prefix": "Send",
- "identities.open_bot_link": "Open @{username} in Telegram",
- "identities.routing_override_prefix": "All messages routed to",
- "identities.routing_override_suffix": "(override active)",
- "identities.agent_scope_status": "Active scope: workspace · status: {status} · selected agent: {agent}",
- "identities.agent_status_loaded": "loaded",
- "identities.agent_status_missing": "missing",
- "identities.agent_status_none": "none",
- "identities.agent_none": "none",
- "identities.peer_id_placeholder_telegram": "e.g. telegram:123456789",
- "identities.peer_id_placeholder_slack": "e.g. slack:U12345678",
-
- // Automations/Scheduled (second pass)
- "scheduled.scheduler_install_requested": "Scheduler install requested.",
- "scheduled.prepared_automation_in_chat": "Prepared automation in chat.",
- "scheduled.prepared_job_in_chat": "Prepared {name} in chat.",
- "scheduled.removed_job": "Removed {name}.",
- "scheduled.delete_confirm_desc": "This removes the schedule and deletes the job definition from {source}.",
+ // ==================== Message List (tool headlines & subagent) ====================
+ "message_list.tool_run_command": "Run {command}",
+ "message_list.tool_run_command_fallback": "Run command",
+ "message_list.tool_reviewed_file": "Reviewed {file}",
+ "message_list.tool_reviewed_file_fallback": "Reviewed file",
+ "message_list.tool_updated_file": "Updated {file}",
+ "message_list.tool_updated_file_fallback": "Updated file",
+ "message_list.tool_update_file": "Update {file}",
+ "message_list.tool_update_file_fallback": "Update file",
+ "message_list.tool_searched_pattern": "Searched {pattern}",
+ "message_list.tool_searched_code_fallback": "Searched code",
+ "message_list.tool_reviewed_path": "Reviewed {path}",
+ "message_list.tool_reviewed_files_fallback": "Reviewed files",
+ "message_list.tool_delegate_agent": "Delegate {agent}",
+ "message_list.tool_delegate_task_fallback": "Delegate task",
+ "message_list.tool_update_todo": "Update todo list",
+ "message_list.tool_read_todo": "Read todo list",
+ "message_list.tool_checked_url": "Checked {url}",
+ "message_list.tool_checked_web_fallback": "Checked web page",
+ "message_list.tool_load_skill_named": "Load skill {name}",
+ "message_list.tool_load_skill_fallback": "Load skill",
+ "message_list.subagent_type_task": "{agentType} task",
+ "message_list.subagent_session_fallback": "Subagent session",
+ "message_list.subagent_loading_transcript": "Loading transcript",
+ "message_list.subagent_running": "Running",
+ "message_list.subagent_message_count": "{count} message{plural}",
+ "message_list.subagent_waiting_transcript": "Waiting for transcript",
+ "message_list.open_session": "Open session",
+ "message_list.step_updates_progress": "Updates progress",
- // Settings (second pass)
- "settings.actor_unknown": "unknown",
- "settings.actor_host": "host",
- "settings.actor_remote": "remote",
- "settings.cap_read": "read",
- "settings.cap_write": "write",
- "settings.workspace_fallback_name": "Workspace",
- "settings.deeplink_hint": "Accepts openwork://, openwork-dev://, or a raw supported https://share.openworklabs.com/b/... URL.",
- "settings.worker_id_label": "Worker {id}",
- "settings.cap_inbox_on": "inbox on",
- "settings.cap_inbox_off": "inbox off",
- "settings.cap_outbox_on": "outbox on",
- "settings.cap_outbox_off": "outbox off",
+ // ==================== Inbox Panel (additional) ====================
+ "inbox_panel.copied_path": "Copied: {path}",
+ "inbox_panel.download_failed": "Download failed",
- // Skills (second pass)
- "skills.loading": "Loading…",
} as const;