@@ -2003,11 +2013,11 @@ function RunsTab({
{/* Execution Prompt */}
@@ -2015,7 +2025,7 @@ function RunsTab({
{/* Token Usage */}
{detailRun.usageJson && (
)}
@@ -2023,7 +2033,7 @@ function RunsTab({
{/* Output */}
{detailRun.stdoutExcerpt && (
-
Output
+
{t("agents.output", "Output")}
{detailRun.stdoutExcerpt.length > 2000
? `${detailRun.stdoutExcerpt.slice(0, 2000)}\n\n... (truncated, ${detailRun.stdoutExcerpt.length} chars total)`
@@ -2035,7 +2045,7 @@ function RunsTab({
{/* Errors */}
{detailRun.stderrExcerpt && (
-
Errors
+
{t("agents.errors", "Errors")}
- Result
+ {t("agents.result", "Result")}
{JSON.stringify(detailRun.resultJson, null, 2)}
)}
@@ -2063,7 +2073,7 @@ function RunsTab({
{/* Context */}
{detailRun.contextSnapshot && Object.keys(detailRun.contextSnapshot).length > 0 && (
-
Context
+
{t("agents.context", "Context")}
{Object.entries(detailRun.contextSnapshot).map(([key, value]) => (
@@ -2077,21 +2087,21 @@ function RunsTab({
{/* No output state */}
{!detailRun.stdoutExcerpt && !detailRun.stderrExcerpt && !detailRun.resultJson && (
- No output captured
+ {t("agents.noOutputCaptured", "No output captured")}
)}
)}
{/* Run Logs */}
-
Agent Logs
+
{t("agents.agentLogs", "Agent Logs")}
{isLoadingLogs ? (
- Loading logs...
+ {t("agents.loadingLogs", "Loading logs...")}
) : runLogs.length === 0 ? (
-
No logs available for this run
+
{t("agents.noLogsForRun", "No logs available for this run")}
) : (
)}
@@ -2124,9 +2134,9 @@ function RunsTab({
{promptSizes.length > 0 && latestPrompt && (
-
Prompt Size
+
{t("agents.promptSize", "Prompt Size")}
-
+
@@ -2138,27 +2148,27 @@ function RunsTab({
)}
{tokenUsageSummary && (
-
Cache hit ratio
+
{t("agents.cacheHitRatio", "Cache hit ratio")}
- {renderCacheWindow("Last 24h", tokenUsageSummary.last24h)}
- {renderCacheWindow("Last 7d", tokenUsageSummary.last7d)}
- {renderCacheWindow("All time", tokenUsageSummary.allTime)}
+ {renderCacheWindow(t("agents.last24h", "Last 24h"), tokenUsageSummary.last24h)}
+ {renderCacheWindow(t("agents.last7d", "Last 7d"), tokenUsageSummary.last7d)}
+ {renderCacheWindow(t("agents.allTime", "All time"), tokenUsageSummary.allTime)}
)}
- {runs.length} run{runs.length !== 1 ? "s" : ""}
- {hasActiveRun && Live }
+ {t("agents.runsCount", { count: runs.length, defaultValue_one: "{{count}} run", defaultValue_other: "{{count}} runs" })}
+ {hasActiveRun && {t("agents.live", "Live")} }
{hasActiveRun && (
void handleStopRun()}
- aria-label={`Stop active run for ${agentName ?? agentId}`}
+ aria-label={t("agents.stopActiveRunFor", "Stop active run for {{name}}", { name: agentName ?? agentId })}
>
- Stop Run
+ {t("agents.stopRun", "Stop Run")}
)}
@@ -2177,15 +2187,6 @@ function formatDuration(start: Date, end: Date): string {
return `${Math.floor(diff / 3600)}h ${Math.floor((diff % 3600) / 60)}m`;
}
-const TASK_COLUMN_LABELS: Record
= {
- triage: "Triage",
- todo: "Todo",
- "in-progress": "In Progress",
- "in-review": "In Review",
- done: "Done",
- archived: "Archived",
-};
-
function truncateTaskLabel(task: Task): string {
const source = task.title?.trim() || task.description?.trim() || task.id;
return source.length > 80 ? `${source.slice(0, 77)}...` : source;
@@ -2200,6 +2201,7 @@ function TasksTab({
projectId?: string;
addToast: (msg: string, type?: "success" | "error") => void;
}) {
+ const { t } = useTranslation("app");
const [tasks, setTasks] = useState([]);
const [isLoading, setIsLoading] = useState(true);
@@ -2216,7 +2218,7 @@ function TasksTab({
.catch((err) => {
if (!cancelled) {
setTasks([]);
- addToast(`Failed to load assigned tasks: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.loadTasksFailed", "Failed to load assigned tasks: {{error}}", { error: getErrorMessage(err) }), "error");
}
})
.finally(() => {
@@ -2234,7 +2236,7 @@ function TasksTab({
return (
-
Loading assigned tasks...
+
{t("agents.loadingTasks", "Loading assigned tasks...")}
);
}
@@ -2243,7 +2245,7 @@ function TasksTab({
return (
-
No tasks assigned to this agent
+
{t("agents.noTasksAssigned", "No tasks assigned to this agent")}
);
}
@@ -2254,13 +2256,22 @@ function TasksTab({
{task.id}
- {TASK_COLUMN_LABELS[task.column]}
+ {
+ ({
+ triage: t("board.triage", "Triage"),
+ todo: t("board.todo", "Todo"),
+ "in-progress": t("board.inProgress", "In Progress"),
+ "in-review": t("board.inReview", "In Review"),
+ done: t("board.done", "Done"),
+ archived: t("board.archived", "Archived"),
+ } as Record)[task.column] ?? task.column
+ }
{truncateTaskLabel(task)}
- {task.status ?? "idle"} · Updated {relativeTime(task.updatedAt)}
+ {task.status ?? "idle"} · {t("agents.taskUpdated", "Updated {{time}}", { time: relativeTime(task.updatedAt) })}
))}
@@ -2368,6 +2379,7 @@ function SoulTab({
addToast: (message: string, type?: "success" | "error") => void;
onSaved: () => Promise;
}) {
+ const { t } = useTranslation("app");
const [soul, setSoul] = useState(agent.soul ?? "");
const [isSaving, setIsSaving] = useState(false);
const [justSaved, setJustSaved] = useState(false);
@@ -2392,14 +2404,14 @@ function SoulTab({
const handleSave = async () => {
if (soul.length > 10000) {
- addToast("Soul must be at most 10,000 characters", "error");
+ addToast(t("agents.soulTooLong", "Soul must be at most 10,000 characters"), "error");
return;
}
setIsSaving(true);
try {
await updateAgentSoul(agent.id, soul, projectId);
- addToast("Soul saved", "success");
+ addToast(t("agents.soulSaved", "Soul saved"), "success");
setJustSaved(true);
if (justSavedTimeoutRef.current) {
clearTimeout(justSavedTimeoutRef.current);
@@ -2407,7 +2419,7 @@ function SoulTab({
justSavedTimeoutRef.current = setTimeout(() => setJustSaved(false), 3000);
await onSaved();
} catch (err) {
- addToast(`Failed to save soul: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.soulSaveFailed", "Failed to save soul: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setIsSaving(false);
}
@@ -2416,33 +2428,33 @@ function SoulTab({
return (
-
Soul
+
{t("agents.soulTitle", "Soul")}
- Define this agent's personality and identity.
+ {t("agents.soulDescription", "Define this agent's personality and identity.")}
-
Agent Soul
+
{t("agents.agentSoulLabel", "Agent Soul")}
setShowPreview(false)}
disabled={!showPreview}
- aria-label="Edit mode"
+ aria-label={t("common.editMode", "Edit mode")}
>
- Edit
+ {t("common.edit", "Edit")}
setShowPreview(true)}
disabled={showPreview}
- aria-label="Preview mode"
+ aria-label={t("common.previewMode", "Preview mode")}
>
- Preview
+ {t("common.preview", "Preview")}
@@ -2455,7 +2467,7 @@ function SoulTab({
) : (
- No soul defined yet. Switch to Edit mode to define the agent's personality.
+ {t("agents.soulEmptyPreview", "No soul defined yet. Switch to Edit mode to define the agent's personality.")}
)
) : (
@@ -2463,7 +2475,7 @@ function SoulTab({
id="agent-soul"
className="input config-textarea-mono"
rows={12}
- placeholder="Describe this agent's personality, tone, and behavioral traits..."
+ placeholder={t("agents.soulPlaceholder", "Describe this agent's personality, tone, and behavioral traits...")}
value={soul}
onChange={(e) => {
setSoul(e.target.value);
@@ -2472,7 +2484,7 @@ function SoulTab({
/>
)}
{!showPreview && (
-
Defines the agent's character and identity. Max 10,000 characters.
+
{t("agents.soulHint", "Defines the agent's character and identity. Max 10,000 characters.")}
)}
@@ -2487,19 +2499,19 @@ function SoulTab({
{isSaving ? (
<>
- Saving…
+ {t("common.saving", "Saving…")}
>
) : (
<>
- Save Soul
+ {t("agents.saveSoul", "Save Soul")}
>
)}
{!hasChanges && justSaved && (
- Soul saved
+ {t("agents.soulSaved", "Soul saved")}
)}
@@ -2520,6 +2532,7 @@ function MemoryTab({
addToast: (message: string, type?: "success" | "error") => void;
onSaved: () => Promise;
}) {
+ const { t } = useTranslation("app");
const [memory, setMemory] = useState(agent.memory ?? "");
const [isSaving, setIsSaving] = useState(false);
const [justSaved, setJustSaved] = useState(false);
@@ -2547,8 +2560,12 @@ function MemoryTab({
);
const selectedLayerDescription = selectedMemoryFile
- ? MEMORY_LAYER_DESCRIPTIONS[selectedMemoryFile.layer]
- : "Select a memory file to view or edit.";
+ ? ({
+ "long-term": t("agents.memoryLayerLongTermDesc", "Curated durable decisions, conventions, constraints, and pitfalls for this specific agent."),
+ daily: t("agents.memoryLayerDailyDesc", "Raw daily observations and open loops recorded by this agent."),
+ dreams: t("agents.memoryLayerDreamsDesc", "Synthesized patterns and emerging themes distilled from this agent's daily memory."),
+ } as Record)[selectedMemoryFile.layer] ?? selectedMemoryFile.layer
+ : t("agents.selectMemoryFile", "Select a memory file to view or edit.");
const loadSelectedMemoryFile = useCallback(async (path: string) => {
setSelectedFileLoading(true);
@@ -2559,7 +2576,7 @@ function MemoryTab({
setSelectedFileDirty(false);
setSelectedFileJustSaved(false);
} catch (err) {
- addToast(`Failed to load agent memory file: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.memoryFileLoadFailed", "Failed to load agent memory file: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setSelectedFileLoading(false);
}
@@ -2581,7 +2598,7 @@ function MemoryTab({
const nextPath = pickDefaultAgentMemoryPath(files, preferredPath);
await loadSelectedMemoryFile(nextPath);
} catch (err) {
- addToast(`Failed to load memory files: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.memoryFilesLoadFailed", "Failed to load memory files: {{error}}", { error: getErrorMessage(err) }), "error");
setMemoryFiles([]);
setSelectedFilePath("");
setSelectedFileContent("");
@@ -2614,14 +2631,14 @@ function MemoryTab({
const handleSaveInlineMemory = async () => {
if (memory.length > 50000) {
- addToast("Memory must be at most 50,000 characters", "error");
+ addToast(t("agents.memoryTooLong", "Memory must be at most 50,000 characters"), "error");
return;
}
setIsSaving(true);
try {
await updateAgentMemory(agent.id, memory, projectId);
- addToast("Memory saved", "success");
+ addToast(t("agents.memorySaved", "Memory saved"), "success");
setJustSaved(true);
if (justSavedTimeoutRef.current) {
clearTimeout(justSavedTimeoutRef.current);
@@ -2629,7 +2646,7 @@ function MemoryTab({
justSavedTimeoutRef.current = setTimeout(() => setJustSaved(false), 3000);
await onSaved();
} catch (err) {
- addToast(`Failed to save memory: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.memorySaveFailed", "Failed to save memory: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setIsSaving(false);
}
@@ -2640,7 +2657,7 @@ function MemoryTab({
return;
}
if (selectedFileDirty) {
- setFileSwitchHint("Save the current file before switching to another file.");
+ setFileSwitchHint(t("agents.saveBeforeSwitch", "Save the current file before switching to another file."));
return;
}
@@ -2664,10 +2681,10 @@ function MemoryTab({
selectedFileJustSavedTimeoutRef.current = setTimeout(() => setSelectedFileJustSaved(false), 3000);
setFileSwitchHint("");
await loadMemoryFiles(selectedFilePath);
- addToast("Agent memory file saved", "success");
+ addToast(t("agents.memoryFileSaved", "Agent memory file saved"), "success");
await onSaved();
} catch (err) {
- addToast(`Failed to save agent memory file: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.memoryFileSaveFailed", "Failed to save agent memory file: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setSavingSelectedFile(false);
}
@@ -2676,21 +2693,21 @@ function MemoryTab({
return (
-
Agent Memory
+
{t("agents.memoryTitle", "Agent Memory")}
- Store context that belongs to this agent only. Workspace memory, daily notes, dreams, and qmd search live in project settings under Project Memory.
+ {t("agents.memoryDescription", "Store context that belongs to this agent only. Workspace memory, daily notes, dreams, and qmd search live in project settings under Project Memory.")}
{isReadOnly && (
- Read-only while this agent is running.
+ {t("agents.memoryReadOnly", "Read-only while this agent is running.")}
)}
-
Inline Memory
+
{t("agents.inlineMemoryLabel", "Inline Memory")}
- Short-form memory stored directly on the agent record and injected into prompts.
+ {t("agents.inlineMemoryHint", "Short-form memory stored directly on the agent record and injected into prompts.")}
@@ -2699,20 +2716,20 @@ function MemoryTab({
className={`btn btn-sm ${!showPreview ? "btn-primary" : ""}`}
onClick={() => setShowPreview(false)}
disabled={!showPreview}
- aria-label="Edit mode"
+ aria-label={t("common.editMode", "Edit mode")}
>
- Edit
+ {t("common.edit", "Edit")}
)}
setShowPreview(true)}
disabled={showPreview}
- aria-label="Preview mode"
+ aria-label={t("common.previewMode", "Preview mode")}
>
- Preview
+ {t("common.preview", "Preview")}
@@ -2725,16 +2742,16 @@ function MemoryTab({
) : (
- No agent memory defined yet. Switch to Edit mode to add memory content.
+ {t("agents.memoryEmptyPreview", "No agent memory defined yet. Switch to Edit mode to add memory content.")}
)
) : (
-
Memory Files
+
{t("agents.memoryFilesLabel", "Memory Files")}
- Full OpenClaw memory files at agent/{agent.name || agent.id}/memory/ (MEMORY.md, DREAMS.md, and daily notes).
+ {t("agents.memoryFilesHint", "Full OpenClaw memory files at")} agent/{agent.name || agent.id}/memory/ {t("agents.memoryFilesHintSuffix", "(MEMORY.md, DREAMS.md, and daily notes).")}
{memoryFiles.length === 0 ? (
- No memory files found
+ {t("agents.noMemoryFiles", "No memory files found")}
) : (
- memoryFiles.map((file) => (
-
- {MEMORY_LAYER_NAMES[file.layer]} • {file.label}
-
- ))
+ memoryFiles.map((file) => {
+ const layerName = ({ "long-term": t("agents.memoryLayerLongTerm", "Long-term"), daily: t("agents.memoryLayerDaily", "Daily"), dreams: t("agents.memoryLayerDreams", "Dreams") } as Record)[file.layer] ?? file.layer;
+ return (
+
+ {layerName} • {file.label}
+
+ );
+ })
)}
{memoryFilesLoading && (
- Loading memory files…
+ {t("agents.loadingMemoryFiles", "Loading memory files…")}
)}
{selectedMemoryFile && (
- {MEMORY_LAYER_NAMES[selectedMemoryFile.layer]} · {selectedLayerDescription}
+ {({ "long-term": t("agents.memoryLayerLongTerm", "Long-term"), daily: t("agents.memoryLayerDaily", "Daily"), dreams: t("agents.memoryLayerDreams", "Dreams") } as Record)[selectedMemoryFile.layer] ?? selectedMemoryFile.layer} · {selectedLayerDescription}
- {selectedMemoryFile.size.toLocaleString()} bytes · Updated {relativeTime(selectedMemoryFile.updatedAt)}
+ {t("agents.memoryFileMeta", "{{size}} bytes · Updated {{time}}", { size: selectedMemoryFile.size.toLocaleString(), time: relativeTime(selectedMemoryFile.updatedAt) })}
)}
@@ -2796,20 +2816,20 @@ function MemoryTab({
className={`btn btn-sm ${!showFilePreview ? "btn-primary" : ""}`}
onClick={() => setShowFilePreview(false)}
disabled={!showFilePreview}
- aria-label="Memory file edit mode"
+ aria-label={t("agents.memoryFileEditMode", "Memory file edit mode")}
>
- Edit
+ {t("common.edit", "Edit")}
)}
setShowFilePreview(true)}
disabled={showFilePreview}
- aria-label="Memory file preview mode"
+ aria-label={t("agents.memoryFilePreviewMode", "Memory file preview mode")}
>
- Preview
+ {t("common.preview", "Preview")}
@@ -2823,14 +2843,14 @@ function MemoryTab({
) : (
- No memory file content yet. Switch to Edit mode to add content.
+ {t("agents.memoryFileEmptyPreview", "No memory file content yet. Switch to Edit mode to add content.")}
)
) : (
@@ -2925,6 +2945,7 @@ function InstructionsTab({
addToast: (message: string, type?: "success" | "error") => void;
onSaved: () => Promise;
}) {
+ const { t } = useTranslation("app");
// Inline instructions state
const [instructionsText, setInstructionsText] = useState(agent.instructionsText ?? "");
const [instructionsPath, setInstructionsPath] = useState(agent.instructionsPath ?? "");
@@ -3013,7 +3034,7 @@ function InstructionsTab({
},
projectId,
);
- addToast("Instructions saved", "success");
+ addToast(t("agents.instructionsSaved", "Instructions saved"), "success");
setJustSaved(true);
if (justSavedTimeoutRef.current) {
clearTimeout(justSavedTimeoutRef.current);
@@ -3021,7 +3042,7 @@ function InstructionsTab({
justSavedTimeoutRef.current = setTimeout(() => setJustSaved(false), 3000);
await onSaved();
} catch (err) {
- addToast(`Failed to save instructions: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.instructionsSaveFailed", "Failed to save instructions: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setIsSaving(false);
}
@@ -3030,14 +3051,14 @@ function InstructionsTab({
const handleSaveFile = async () => {
const path = instructionsPath.trim();
if (!path) {
- addToast("No instructions file path set", "error");
+ addToast(t("agents.noInstructionsPath", "No instructions file path set"), "error");
return;
}
setIsSavingFile(true);
try {
await saveWorkspaceFileContent("project", path, fileContent);
- addToast("Instructions file saved", "success");
+ addToast(t("agents.instructionsFileSaved", "Instructions file saved"), "success");
setFileContentDirty(false);
setJustSavedFile(true);
if (justSavedFileTimeoutRef.current) {
@@ -3046,7 +3067,7 @@ function InstructionsTab({
justSavedFileTimeoutRef.current = setTimeout(() => setJustSavedFile(false), 3000);
await onSaved();
} catch (err) {
- addToast(`Failed to save instructions file: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.instructionsFileSaveFailed", "Failed to save instructions file: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setIsSavingFile(false);
}
@@ -3057,9 +3078,9 @@ function InstructionsTab({
return (
-
Custom Instructions
+
{t("agents.instructionsTitle", "Custom Instructions")}
- Append custom instructions to this agent's system prompt at execution time. Use this to customize behavior, coding style, or project conventions without modifying built-in prompts.
+ {t("agents.instructionsDescription", "Append custom instructions to this agent's system prompt at execution time. Use this to customize behavior, coding style, or project conventions without modifying built-in prompts.")}
@@ -3071,21 +3092,21 @@ function InstructionsTab({
className={`btn btn-sm ${!showPreview ? "btn-primary" : ""}`}
onClick={() => setShowPreview(false)}
disabled={!showPreview}
- aria-label="Edit mode"
+ aria-label={t("common.editMode", "Edit mode")}
data-testid="instructions-edit-toggle"
>
- Edit
+ {t("common.edit", "Edit")}
setShowPreview(true)}
disabled={showPreview}
- aria-label="Preview mode"
+ aria-label={t("common.previewMode", "Preview mode")}
data-testid="instructions-preview-toggle"
>
- Preview
+ {t("common.preview", "Preview")}
@@ -3098,7 +3119,7 @@ function InstructionsTab({
) : (
- No inline instructions defined yet. Switch to Edit mode to add instructions.
+ {t("agents.instructionsEmptyPreview", "No inline instructions defined yet. Switch to Edit mode to add instructions.")}
)
) : (
@@ -3106,7 +3127,7 @@ function InstructionsTab({
id="instructions-text"
className="input"
rows={10}
- placeholder="Enter custom instructions to append to this agent's system prompt..."
+ placeholder={t("agents.instructionsPlaceholder", "Enter custom instructions to append to this agent's system prompt...")}
value={instructionsText}
onChange={(e) => {
setInstructionsText(e.target.value);
@@ -3115,24 +3136,24 @@ function InstructionsTab({
/>
)}
{!showPreview && (
- Markdown formatting supported. Max 50,000 characters.
+ {t("agents.instructionsHint", "Markdown formatting supported. Max 50,000 characters.")}
)}
- Instructions File Path
+ {t("agents.instructionsPathLabel", "Instructions File Path")}
{
setInstructionsPath(e.target.value);
setJustSaved(false);
}}
/>
- Path to a .md file (relative to project root). Contents are read and appended at execution time.
+ {t("agents.instructionsPathHint", "Path to a .md file (relative to project root). Contents are read and appended at execution time.")}
@@ -3146,19 +3167,19 @@ function InstructionsTab({
{isSaving ? (
<>
- Saving…
+ {t("common.saving", "Saving…")}
>
) : (
<>
- Save Instructions
+ {t("agents.saveInstructions", "Save Instructions")}
>
)}
{!hasInstructionsChanges && justSaved && (
- Instructions saved
+ {t("agents.instructionsSaved", "Instructions saved")}
)}
@@ -3167,24 +3188,24 @@ function InstructionsTab({
{hasFilePath && (
-
Instructions File Editor
+
{t("agents.instructionsFileEditorTitle", "Instructions File Editor")}
- Edit the instructions file directly. Changes are saved separately from the path configuration.
+ {t("agents.instructionsFileEditorDesc", "Edit the instructions file directly. Changes are saved separately from the path configuration.")}
- File Content
+ {t("agents.fileContentLabel", "File Content")}
{isLoadingFile && (
- Loading...
+ {t("common.loading", "Loading...")}
)}
{fileContentDirty && !isLoadingFile && (
- Unsaved changes
+ {t("common.unsavedChanges", "Unsaved changes")}
)}
@@ -3192,7 +3213,7 @@ function InstructionsTab({
id="instructions-file-content"
className="input config-textarea-mono"
rows={20}
- placeholder="File content will appear here when loaded..."
+ placeholder={t("agents.fileContentPlaceholder", "File content will appear here when loaded...")}
value={fileContent}
readOnly={isLoadingFile}
onChange={(e) => {
@@ -3201,7 +3222,7 @@ function InstructionsTab({
setJustSavedFile(false);
}}
/>
-
Edit the markdown file content directly. Save separately using the button below.
+
{t("agents.fileContentHint", "Edit the markdown file content directly. Save separately using the button below.")}
@@ -3214,19 +3235,19 @@ function InstructionsTab({
{isSavingFile ? (
<>
- Saving…
+ {t("common.saving", "Saving…")}
>
) : (
<>
- Save File
+ {t("agents.saveFile", "Save File")}
>
)}
{!fileContentDirty && justSavedFile && (
- File saved
+ {t("agents.fileSaved", "File saved")}
)}
@@ -3325,6 +3346,7 @@ function HeartbeatProcedureSection({
addToast: (message: string, type?: "success" | "error") => void;
onSaved: () => Promise
;
}) {
+ const { t } = useTranslation("app");
const [isUpgrading, setIsUpgrading] = useState(false);
const [showFileViewer, setShowFileViewer] = useState(false);
const [isLoadingFile, setIsLoadingFile] = useState(false);
@@ -3363,7 +3385,7 @@ function HeartbeatProcedureSection({
} catch (err) {
const message = getErrorMessage(err);
setFileLoadError(message);
- addToast(`Failed to load heartbeat procedure file: ${message}`, "error");
+ addToast(t("agents.heartbeatFileLoadFailed", "Failed to load heartbeat procedure file: {{error}}", { error: message }), "error");
} finally {
setIsLoadingFile(false);
}
@@ -3401,14 +3423,14 @@ function HeartbeatProcedureSection({
await saveWorkspaceFileContent("project", currentPath, fileContent, projectId);
setFileContentDirty(false);
setJustSavedFile(true);
- addToast("Heartbeat procedure file saved", "success");
+ addToast(t("agents.heartbeatFileSaved", "Heartbeat procedure file saved"), "success");
if (justSavedFileTimeoutRef.current) {
clearTimeout(justSavedFileTimeoutRef.current);
}
justSavedFileTimeoutRef.current = setTimeout(() => setJustSavedFile(false), 3000);
await onSaved();
} catch (err) {
- addToast(`Failed to save heartbeat procedure file: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.heartbeatFileSaveFailed", "Failed to save heartbeat procedure file: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setIsSavingFile(false);
}
@@ -3420,13 +3442,13 @@ function HeartbeatProcedureSection({
const result = await upgradeAgentHeartbeatProcedure(agent.id, projectId);
addToast(
result.procedureFileSeeded
- ? `Heartbeat procedure file ready at ${result.heartbeatProcedurePath}`
- : `Heartbeat procedure path set to ${result.heartbeatProcedurePath}`,
+ ? t("agents.heartbeatProcedureFileReady", "Heartbeat procedure file ready at {{path}}", { path: result.heartbeatProcedurePath })
+ : t("agents.heartbeatProcedurePathSet", "Heartbeat procedure path set to {{path}}", { path: result.heartbeatProcedurePath }),
"success",
);
await onSaved();
} catch (err) {
- addToast(`Failed to upgrade heartbeat procedure: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.heartbeatUpgradeFailed", "Failed to upgrade heartbeat procedure: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setIsUpgrading(false);
}
@@ -3434,17 +3456,14 @@ function HeartbeatProcedureSection({
return (
-
Heartbeat Procedure
+
{t("agents.heartbeatProcedureTitle", "Heartbeat Procedure")}
- The per-tick procedure this agent runs every wake. Defaults to a per-agent
- markdown file (for example .fusion/agents/ceo-agent2736/HEARTBEAT.md)
- that you can edit. Legacy id-only default paths remain valid. Resets on every tick —
- no need to restart the agent after editing.
+ {t("agents.heartbeatProcedureDesc", "The per-tick procedure this agent runs every wake. Defaults to a per-agent markdown file (for example")} .fusion/agents/ceo-agent2736/HEARTBEAT.md{t("agents.heartbeatProcedureDescSuffix", ") that you can edit. Legacy id-only default paths remain valid. Resets on every tick — no need to restart the agent after editing.")}
- Current path: {currentPath || "(none — using built-in default)"}
+ {t("agents.currentPath", "Current path:")} {currentPath || t("agents.noneUsingBuiltIn", "(none — using built-in default)")}
{hasFilePath && (
@@ -3456,12 +3475,12 @@ function HeartbeatProcedureSection({
{isLoadingFile ? (
<>
- Loading file…
+ {t("agents.loadingFile", "Loading file…")}
>
) : (
<>
- View Heartbeat Markdown
+ {t("agents.viewHeartbeatMarkdown", "View Heartbeat Markdown")}
>
)}
@@ -3473,28 +3492,26 @@ function HeartbeatProcedureSection({
className="btn"
disabled={isUpgrading || onDefault}
onClick={() => void handleUpgrade()}
- aria-label="Upgrade agent to default heartbeat procedure file"
+ aria-label={t("agents.upgradeToDefaultAriaLabel", "Upgrade agent to default heartbeat procedure file")}
>
{isUpgrading ? (
<>
- Upgrading…
+ {t("agents.upgrading", "Upgrading…")}
>
) : onDefault ? (
<>
- Already on default
+ {t("agents.alreadyOnDefault", "Already on default")}
>
) : (
- "Upgrade to Default Heartbeat Procedure"
+ t("agents.upgradeToDefault", "Upgrade to Default Heartbeat Procedure")
)}
- Sets heartbeatProcedurePath to{" "}
+ {t("agents.upgradeHint", "Sets")} heartbeatProcedurePath {t("agents.upgradeHintTo", "to")}{" "}
{canonicalDefaultPath}
- {" "}and seeds the file from the built-in template if it doesn't exist.
- Each agent gets its own per-agent file, so edits stay scoped to this agent.
- Operator edits to the file are preserved.
+ {" "}{t("agents.upgradeHintSuffix", "and seeds the file from the built-in template if it doesn't exist. Each agent gets its own per-agent file, so edits stay scoped to this agent. Operator edits to the file are preserved.")}
@@ -3502,37 +3519,37 @@ function HeartbeatProcedureSection({
{showFileViewer && hasFilePath && currentPath && (
-
Heartbeat Procedure File
+
{t("agents.heartbeatProcedureFileLabel", "Heartbeat Procedure File")}
setShowPreview(false)}
disabled={!showPreview}
- aria-label="Heartbeat file edit mode"
+ aria-label={t("agents.heartbeatFileEditMode", "Heartbeat file edit mode")}
>
- Edit
+ {t("common.edit", "Edit")}
setShowPreview(true)}
disabled={showPreview}
- aria-label="Heartbeat file preview mode"
+ aria-label={t("agents.heartbeatFilePreviewMode", "Heartbeat file preview mode")}
>
- Preview
+ {t("common.preview", "Preview")}
{isLoadingFile && (
- Loading...
+ {t("common.loading", "Loading...")}
)}
{fileContentDirty && !isLoadingFile && (
- Unsaved changes
+ {t("common.unsavedChanges", "Unsaved changes")}
)}
@@ -3543,7 +3560,7 @@ function HeartbeatProcedureSection({
) : (
- No heartbeat procedure markdown content yet.
+ {t("agents.heartbeatFileEmptyPreview", "No heartbeat procedure markdown content yet.")}
)
) : (
@@ -3553,7 +3570,7 @@ function HeartbeatProcedureSection({
rows={16}
value={fileContent}
readOnly={isLoadingFile}
- placeholder="Heartbeat procedure markdown file content will appear here..."
+ placeholder={t("agents.heartbeatFilePlaceholder", "Heartbeat procedure markdown file content will appear here...")}
onChange={(e) => {
setFileContent(e.target.value);
setFileContentDirty(true);
@@ -3562,10 +3579,10 @@ function HeartbeatProcedureSection({
/>
)}
{fileLoadError && (
-
Failed to load file: {fileLoadError}
+
{t("agents.fileLoadError", "Failed to load file: {{error}}", { error: fileLoadError })}
)}
- This editor writes directly to {currentPath}.
+ {t("agents.heartbeatFileEditorHint", "This editor writes directly to")} {currentPath}.
{!showPreview && (
@@ -3578,19 +3595,19 @@ function HeartbeatProcedureSection({
{isSavingFile ? (
<>
- Saving…
+ {t("common.saving", "Saving…")}
>
) : (
<>
- Save Heartbeat File
+ {t("agents.saveHeartbeatFile", "Save Heartbeat File")}
>
)}
{!fileContentDirty && justSavedFile && (
- File saved
+ {t("agents.fileSaved", "File saved")}
)}
@@ -3618,6 +3635,7 @@ function ConfigTab({
onDelete?: () => Promise
| void;
onAgentDraftApplied?: (updates: Partial) => void;
}) {
+ const { t } = useTranslation("app");
// Identity field state
const [nameValue, setNameValue] = useState(agent.name);
const [roleValue, setRoleValue] = useState(agent.role);
@@ -3798,7 +3816,7 @@ function ConfigTab({
onAgentDraftApplied?.(draftUpdates);
setIsAiInterviewOpen(false);
- addToast("Interview draft applied. Review and save when ready.", "success");
+ addToast(t("agents.interviewDraftApplied", "Interview draft applied. Review and save when ready."), "success");
}, [addToast, onAgentDraftApplied]);
useEffect(() => {
@@ -3816,9 +3834,9 @@ function ConfigTab({
try {
await updateAgent(agent.id, { permissionPolicy: next }, projectId);
await onSaved();
- addToast("Permission policy updated", "success");
+ addToast(t("agents.permissionPolicyUpdated", "Permission policy updated"), "success");
} catch (err) {
- addToast(`Failed to update permission policy: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.permissionPolicyFailed", "Failed to update permission policy: {{error}}", { error: getErrorMessage(err) }), "error");
}
};
@@ -3884,12 +3902,12 @@ function ConfigTab({
setIsResettingBudget(true);
try {
await resetAgentBudget(agent.id, projectId);
- addToast("Budget usage reset successfully", "success");
+ addToast(t("agents.budgetResetSuccess", "Budget usage reset successfully"), "success");
// Refresh budget status
const status = await fetchAgentBudgetStatus(agent.id, projectId);
setBudgetStatus(status);
} catch (err) {
- addToast(`Failed to reset budget: ${getErrorMessage(err)}`, "error");
+ addToast(t("agents.budgetResetFailed", "Failed to reset budget: {{error}}", { error: getErrorMessage(err) }), "error");
} finally {
setIsResettingBudget(false);
}
@@ -4293,10 +4311,10 @@ function ConfigTab({
if (!payload) {
setErrors(validationErrors);
if (showValidationToast) {
- addToast("Please fix validation errors before saving", "error");
+ addToast(t("agents.fixValidationErrors", "Please fix validation errors before saving"), "error");
}
if (source === "auto") {
- setAutoSaveError("Fix validation errors to save changes");
+ setAutoSaveError(t("agents.fixValidationToSave", "Fix validation errors to save changes"));
}
return false;
}
@@ -4317,7 +4335,7 @@ function ConfigTab({
}
lastSavedSignatureRef.current = signature;
if (source === "manual") {
- addToast("Settings saved", "success");
+ addToast(t("agents.settingsSaved", "Settings saved"), "success");
}
setAutoSaveError(null);
setJustSaved(true);
@@ -4331,7 +4349,7 @@ function ConfigTab({
if (revision === saveRevisionRef.current) {
const message = getErrorMessage(err);
setAutoSaveError(message);
- addToast(`Failed to save settings: ${message}`, "error");
+ addToast(t("agents.settingsSaveFailed", "Failed to save settings: {{error}}", { error: message }), "error");
}
return false;
} finally {
@@ -4380,7 +4398,7 @@ function ConfigTab({
try {
await uploadAgentAvatar(agent.id, file, projectId);
await onSaved();
- addToast("Avatar uploaded", "success");
+ addToast(t("agents.avatarUploaded", "Avatar uploaded"), "success");
} catch (error: unknown) {
addToast(getErrorMessage(error), "error");
} finally {
@@ -4396,7 +4414,7 @@ function ConfigTab({
try {
await deleteAgentAvatar(agent.id, projectId);
await onSaved();
- addToast("Avatar removed", "success");
+ addToast(t("agents.avatarRemoved", "Avatar removed"), "success");
} catch (error: unknown) {
addToast(getErrorMessage(error), "error");
} finally {
@@ -4405,29 +4423,29 @@ function ConfigTab({
}, [addToast, agent.id, onSaved, projectId]);
const saveStatusLabel = isSaving
- ? "Saving changes…"
+ ? t("agents.savingChanges", "Saving changes…")
: autoSaveError
- ? `Save failed: ${autoSaveError}`
+ ? t("agents.saveFailed", "Save failed: {{error}}", { error: autoSaveError })
: !hasChanges && justSaved
- ? "All changes saved"
+ ? t("agents.allChangesSaved", "All changes saved")
: null;
return (
-
Agent Configuration
+
{t("agents.configTitle", "Agent Configuration")}
- Configure agent settings and behavior.
+ {t("agents.configDescription", "Configure agent settings and behavior.")}
setIsAiInterviewOpen(true)}>
- AI Interview
+ {t("agents.aiInterview", "AI Interview")}
-
Name
+
{t("agents.nameLabel", "Name")}
- Role
+ {t("agents.roleLabel2", "Role")}
- Triage
- Executor
- Reviewer
- Merger
- Scheduler
- Custom
+ {t("agents.roleTriage", "Triage")}
+ {t("agents.roleExecutor", "Executor")}
+ {t("agents.roleReviewer", "Reviewer")}
+ {t("agents.roleMerger", "Merger")}
+ {t("agents.roleScheduler", "Scheduler")}
+ {t("agents.roleCustom", "Custom")}
- Title
+ {t("agents.titleLabel", "Title")}
setTitleValue(e.target.value)}
onBlur={() => { void scheduleAutoSave(); }}
@@ -4472,7 +4490,7 @@ function ConfigTab({
-
Avatar
+
{t("agents.avatarLabel", "Avatar")}
@@ -4496,11 +4514,11 @@ function ConfigTab({
disabled={isAvatarPending}
onClick={() => avatarInputRef.current?.click()}
>
- Upload Avatar
+ {t("agents.uploadAvatar", "Upload Avatar")}
{agent.imageUrl ? (
void handleAvatarDelete()} disabled={isAvatarPending}>
- Remove Avatar
+ {t("agents.removeAvatar", "Remove Avatar")}
) : null}
@@ -4508,12 +4526,12 @@ function ConfigTab({
- Icon
+ {t("agents.iconLabel", "Icon")}
setIconValue(e.target.value)}
onBlur={() => { void scheduleAutoSave(); }}
@@ -4521,7 +4539,7 @@ function ConfigTab({
- Reports To
+ {t("agents.reportsToLabel", "Reports To")}
- No manager
+ {t("agents.noManager", "No manager")}
{hasMissingManagerSelection && (
- Unknown manager ({managerSelection})
+ {t("agents.unknownManager", "Unknown manager ({{id}})", { id: managerSelection })}
)}
{availableManagers.map((manager) => (
@@ -4547,9 +4565,9 @@ function ConfigTab({
-
Skills
+
{t("agents.skillsTitle", "Skills")}
- Assign skills to this agent for specialized behavior.
+ {t("agents.skillsDescription", "Assign skills to this agent for specialized behavior.")}
@@ -4569,15 +4587,15 @@ function ConfigTab({
-
Model
+
{t("agents.modelTitle", "Model")}
- Choose either a built-in model or a plugin runtime for this agent. These options are mutually exclusive.
+ {t("agents.modelDescription", "Choose either a built-in model or a plugin runtime for this agent. These options are mutually exclusive.")}
-
Runtime Source
-
+
{t("agents.runtimeSource", "Runtime Source")}
+
- Built-in Model
+ {t("agents.builtInModel", "Built-in Model")}
- Plugin Runtime
+ {t("agents.pluginRuntime", "Plugin Runtime")}
@@ -4617,8 +4635,8 @@ function ConfigTab({
setModelValue(value);
void scheduleAutoSave();
}}
- placeholder="Use global default"
- label="Agent Model"
+ placeholder={t("agents.useGlobalDefault", "Use global default")}
+ label={t("agents.agentModelLabel", "Agent Model")}
disabled={modelsLoading}
favoriteProviders={favoriteProviders}
onToggleFavorite={toggleFavoriteProvider}
@@ -4628,9 +4646,9 @@ function ConfigTab({
) : (
- Runtime
+ {t("agents.runtimeLabel", "Runtime")}
{runtimesLoading ? (
- Loading runtimes…
+ {t("agents.loadingRuntimes", "Loading runtimes…")}
) : (
- {availableRuntimes.length > 0 ? "Select a plugin runtime…" : "No plugin runtimes available"}
+ {availableRuntimes.length > 0 ? t("agents.selectRuntime", "Select a plugin runtime…") : t("agents.noRuntimes", "No plugin runtimes available")}
{availableRuntimes.map((runtime) => (
@@ -4657,13 +4675,13 @@ function ConfigTab({
-
Permissions
+
{t("agents.permissionsTitle", "Permissions")}
- Per-agent settings override project defaults. Each category controls a separate approval gate.
+ {t("agents.permissionsDescription", "Per-agent settings override project defaults. Each category controls a separate approval gate.")}
{permissionPolicyValue === undefined ? (
- Inheriting project default — no per-agent override set
+ {t("agents.inheritingProjectDefault", "Inheriting project default — no per-agent override set")}
- Customize for this agent
+ {t("agents.customizeForAgent", "Customize for this agent")}
) : null}
@@ -4692,17 +4710,17 @@ function ConfigTab({
-
Heartbeat Settings
+
{t("agents.heartbeatSettingsTitle", "Heartbeat Settings")}
- Configure how this agent's heartbeat is monitored. Leave a field empty to use system defaults.
+ {t("agents.heartbeatSettingsDesc", "Configure how this agent's heartbeat is monitored. Leave a field empty to use system defaults.")}
- Coordination-only agent
- Disables auto-claim and removes the candidate section from heartbeat prompts. Recommended for routing/CEO-style agents.
+ {t("agents.coordinationOnlyAgent", "Coordination-only agent")}
+ {t("agents.coordinationOnlyHint", "Disables auto-claim and removes the candidate section from heartbeat prompts. Recommended for routing/CEO-style agents.")}
- Apply preset
+ {t("agents.applyPreset", "Apply preset")}
@@ -4726,9 +4744,9 @@ function ConfigTab({
void scheduleAutoSave();
}}
/>
- Auto-Claim Relevant Tasks
+ {t("agents.autoClaimRelevantTasks", "Auto-Claim Relevant Tasks")}
-
When enabled (default), no-task heartbeats scan open unowned work and auto-claim tasks aligned with this agent's role and soul.
+
{t("agents.autoClaimHint", "When enabled (default), no-task heartbeats scan open unowned work and auto-claim tasks aligned with this agent's role and soul.")}
@@ -4742,9 +4760,9 @@ function ConfigTab({
void scheduleAutoSave();
}}
/>
- Heartbeat Enabled
+ {t("agents.heartbeatEnabled", "Heartbeat Enabled")}
- When enabled, this agent receives scheduled heartbeat runs based on its interval.
+ {t("agents.heartbeatEnabledHint", "When enabled, this agent receives scheduled heartbeat runs based on its interval.")}
@@ -4758,9 +4776,9 @@ function ConfigTab({
void scheduleAutoSave();
}}
/>
- Run Missed Heartbeat On Startup
+ {t("agents.runMissedHeartbeat", "Run Missed Heartbeat On Startup")}
- When enabled, if the server was down across this agent's scheduled heartbeat tick, fire a single catch-up heartbeat at startup. Default: off.
+ {t("agents.runMissedHeartbeatHint", "When enabled, if the server was down across this agent's scheduled heartbeat tick, fire a single catch-up heartbeat at startup. Default: off.")}
@@ -4774,9 +4792,9 @@ function ConfigTab({
void scheduleAutoSave();
}}
/>
- Allow Parallel Execution
+ {t("agents.allowParallelExecution", "Allow Parallel Execution")}
- When disabled, the heartbeat and task execution paths serialize for this agent (heartbeat will not start while the agent's task is executing, and vice versa). Permanent agents only.
+ {t("agents.allowParallelExecutionHint", "When disabled, the heartbeat and task execution paths serialize for this agent (heartbeat will not start while the agent's task is executing, and vice versa). Permanent agents only.")}
@@ -4790,13 +4808,13 @@ function ConfigTab({
void scheduleAutoSave();
}}
/>
- Skip heartbeat when idle
+ {t("agents.skipHeartbeatWhenIdle", "Skip heartbeat when idle")}
- When enabled, scheduled (timer) heartbeats are skipped while this agent has no assigned task. The agent still wakes immediately when a task is assigned or you trigger a run manually. Default: off.
+ {t("agents.skipHeartbeatWhenIdleHint", "When enabled, scheduled (timer) heartbeats are skipped while this agent has no assigned task. The agent still wakes immediately when a task is assigned or you trigger a run manually. Default: off.")}
- Heartbeat Scope Discipline
+ {t("agents.heartbeatScopeDiscipline", "Heartbeat Scope Discipline")}
- Inherit project default
- Strict
- Lite
- Off
+ {t("agents.inheritProjectDefault", "Inherit project default")}
+ {t("agents.strict", "Strict")}
+ {t("agents.lite", "Lite")}
+ {t("agents.off", "Off")}
- Strict — coordination-focused; higher per-tick tokens. Lite — pre-2026-05-11 behavior. Off — minimal procedure.
+ {t("agents.scopeDisciplineHint", "Strict — coordination-focused; higher per-tick tokens. Lite — pre-2026-05-11 behavior. Off — minimal procedure.")}
- Heartbeat Prompt Template
+ {t("agents.heartbeatPromptTemplate", "Heartbeat Prompt Template")}
- Inherit project default
- Default
- Compact
+ {t("agents.inheritProjectDefault", "Inherit project default")}
+ {t("agents.templateDefault", "Default")}
+ {t("agents.templateCompact", "Compact")}
- Heartbeat Interval (s)
+ {t("agents.heartbeatIntervalLabel", "Heartbeat Interval (s)")}
{errors.heartbeatIntervalMs}
) : (
- How often heartbeats are checked. Leave empty for system default ({DEFAULT_HEARTBEAT_INTERVAL_MS / 1000}s / {DEFAULT_HEARTBEAT_INTERVAL_LABEL}).
+ {t("agents.heartbeatIntervalHint", "How often heartbeats are checked. Leave empty for system default ({{seconds}}s / {{label}}).", { seconds: DEFAULT_HEARTBEAT_INTERVAL_MS / 1000, label: DEFAULT_HEARTBEAT_INTERVAL_LABEL })}
)}
- Heartbeat Timeout (s)
+ {t("agents.heartbeatTimeoutLabel", "Heartbeat Timeout (s)")}
{errors.heartbeatTimeoutMs}
) : (
- Time without heartbeat before agent is considered unresponsive. Leave empty for system default (60s)
+ {t("agents.heartbeatTimeoutHint", "Time without heartbeat before agent is considered unresponsive. Leave empty for system default (60s)")}
)}
- Max Concurrent Runs
+ {t("agents.maxConcurrentRunsLabel", "Max Concurrent Runs")}
{errors.maxConcurrentRuns}
) : (
- Maximum simultaneous heartbeat runs for this agent. Leave empty for system default (1).
+ {t("agents.maxConcurrentRunsHint", "Maximum simultaneous heartbeat runs for this agent. Leave empty for system default (1).")}
)}
- Message Response Mode
+ {t("agents.messageResponseModeLabel", "Message Response Mode")}
handleHeartbeatFieldChange("messageResponseMode", e.target.value)}
>
- System Default (On Heartbeat)
- On Heartbeat
- Immediate
+ {t("agents.systemDefaultOnHeartbeat", "System Default (On Heartbeat)")}
+ {t("agents.onHeartbeat", "On Heartbeat")}
+ {t("agents.immediate", "Immediate")}
{errors.messageResponseMode ? (
{errors.messageResponseMode}
) : (
- How this agent responds to incoming messages. 'Immediate' wakes the agent as soon as a message arrives. 'On Heartbeat' defers processing to the next scheduled heartbeat.
+ {t("agents.messageResponseModeHint", "How this agent responds to incoming messages. 'Immediate' wakes the agent as soon as a message arrives. 'On Heartbeat' defers processing to the next scheduled heartbeat.")}
)}
-
Budget Settings
+
{t("agents.budgetSettingsTitle", "Budget Settings")}
- Configure token budget limits for this agent. Leave all fields empty to disable budget tracking.
+ {t("agents.budgetSettingsDesc", "Configure token budget limits for this agent. Leave all fields empty to disable budget tracking.")}
- Token Budget
+ {t("agents.tokenBudgetLabel", "Token Budget")}
handleBudgetFieldChange("tokenBudget", e.target.value)}
/>
{errors.tokenBudget ? (
{errors.tokenBudget}
) : (
- Total token cap (input + output) for this agent. Leave empty for no limit.
+ {t("agents.tokenBudgetHint", "Total token cap (input + output) for this agent. Leave empty for no limit.")}
)}
- Usage Threshold (%)
+ {t("agents.usageThresholdLabel", "Usage Threshold (%)")}
{errors.usageThreshold}
) : (
- Warning threshold as a percentage. Agent warns when usage reaches this level. Default: 80%.
+ {t("agents.usageThresholdHint", "Warning threshold as a percentage. Agent warns when usage reaches this level. Default: 80%.")}
)}
- Budget Period
+ {t("agents.budgetPeriodLabel", "Budget Period")}
handleBudgetFieldChange("budgetPeriod", e.target.value)}
>
- No reset (lifetime)
- Daily
- Weekly
- Monthly
+ {t("agents.noReset", "No reset (lifetime)")}
+ {t("agents.daily", "Daily")}
+ {t("agents.weekly", "Weekly")}
+ {t("agents.monthly", "Monthly")}
{errors.budgetPeriod ? (
{errors.budgetPeriod}
) : (
- How often the budget counter resets. Leave empty for lifetime budget.
+ {t("agents.budgetPeriodHint", "How often the budget counter resets. Leave empty for lifetime budget.")}
)}
- Reset Day
+ {t("agents.resetDayLabel", "Reset Day")}
handleBudgetFieldChange("resetDay", e.target.value)}
/>
@@ -4989,10 +5007,10 @@ function ConfigTab({
) : (
{budgetValues.budgetPeriod === "weekly"
- ? "Day of week (0=Sunday to 6=Saturday) for reset."
+ ? t("agents.resetDayWeekly", "Day of week (0=Sunday to 6=Saturday) for reset.")
: budgetValues.budgetPeriod === "monthly"
- ? "Day of month (1-31) for reset."
- : "Day for reset (weekly: 0-6, monthly: 1-31). Leave empty for automatic."}
+ ? t("agents.resetDayMonthly", "Day of month (1-31) for reset.")
+ : t("agents.resetDayHint", "Day for reset (weekly: 0-6, monthly: 1-31). Leave empty for automatic.")}
)}
@@ -5000,7 +5018,7 @@ function ConfigTab({
{/* Budget Usage Progress Bar */}
{budgetStatus?.budgetLimit != null && (
-
Current Usage
+
{t("agents.currentUsage", "Current Usage")}
- {(budgetStatus.currentUsage ?? 0).toLocaleString()} / {(budgetStatus.budgetLimit ?? 0).toLocaleString()} tokens ({Math.round(budgetStatus.usagePercent ?? 0)}% used)
+ {t("agents.budgetUsageDisplay", "{{used}} / {{limit}} tokens ({{percent}}% used)", { used: (budgetStatus.currentUsage ?? 0).toLocaleString(), limit: (budgetStatus.budgetLimit ?? 0).toLocaleString(), percent: Math.round(budgetStatus.usagePercent ?? 0) })}
@@ -5033,12 +5051,12 @@ function ConfigTab({
{isResettingBudget ? (
<>
- Resetting…
+ {t("agents.resetting", "Resetting…")}
>
) : (
<>
- Reset Budget Usage
+ {t("agents.resetBudgetUsage", "Reset Budget Usage")}
>
)}
@@ -5048,35 +5066,35 @@ function ConfigTab({
-
Instruction Bundle
+
{t("agents.bundleTitle", "Instruction Bundle")}
- Configure the agent's instruction bundle. Leave empty to use inline instructions only.
+ {t("agents.bundleDescription", "Configure the agent's instruction bundle. Leave empty to use inline instructions only.")}
- Bundle Mode
+ {t("agents.bundleModeLabel", "Bundle Mode")}
setBundleMode(e.target.value)}
>
- None (use inline instructions)
- Managed (system-managed directory)
- External (user-specified path)
+ {t("agents.bundleNone", "None (use inline instructions)")}
+ {t("agents.bundleManaged", "Managed (system-managed directory)")}
+ {t("agents.bundleExternal", "External (user-specified path)")}
- {bundleMode === "managed" && "Files will be stored in a system-managed directory within .fusion/agents/"}
- {bundleMode === "external" && "Specify an external directory path for the instruction files"}
- {!bundleMode && "Select a mode to enable instruction bundling"}
+ {bundleMode === "managed" && t("agents.bundleManagedHint", "Files will be stored in a system-managed directory within .fusion/agents/")}
+ {bundleMode === "external" && t("agents.bundleExternalHint", "Specify an external directory path for the instruction files")}
+ {!bundleMode && t("agents.bundleSelectMode", "Select a mode to enable instruction bundling")}
{bundleMode && (
<>
- Entry File
+ {t("agents.bundleEntryFileLabel", "Entry File")}
setBundleEntryFile(e.target.value)}
/>
- Primary instructions file name (default: AGENTS.md)
+ {t("agents.bundleEntryFileHint", "Primary instructions file name (default: AGENTS.md)")}
{bundleMode === "external" && (
- External Path
+ {t("agents.bundleExternalPathLabel", "External Path")}
setBundleExternalPath(e.target.value)}
/>
- Absolute or relative path to the external directory
+ {t("agents.bundleExternalPathHint", "Absolute or relative path to the external directory")}
)}
- Files (comma-separated)
+ {t("agents.bundleFilesLabel", "Files (comma-separated)")}
f.trim()).filter(Boolean)
)}
/>
- List of file names in the bundle directory
+ {t("agents.bundleFilesHint", "List of file names in the bundle directory")}
>
)}
@@ -5123,9 +5141,9 @@ function ConfigTab({
-
Advanced Settings
+
{t("agents.advancedSettingsTitle", "Advanced Settings")}
- Advanced configuration options for this agent. Leave a field empty to use system defaults.
+ {t("agents.advancedSettingsDesc", "Advanced configuration options for this agent. Leave a field empty to use system defaults.")}
@@ -5141,7 +5159,7 @@ function ConfigTab({
value={formValues[field.key] ?? ""}
onChange={(e) => handleFieldChange(field.key, e.target.value)}
>
-
System Default
+
{t("agents.systemDefault", "System Default")}
{field.options?.map((opt) => (
{opt.label}
@@ -5179,12 +5197,12 @@ function ConfigTab({
{isSaving ? (
<>
- Saving…
+ {t("common.saving", "Saving…")}
>
) : (
<>
- Save Settings
+ {t("agents.saveSettings", "Save Settings")}
>
)}
@@ -5205,9 +5223,9 @@ function ConfigTab({
/>
-
Danger Zone
+
{t("agents.dangerZone", "Danger Zone")}
- Permanently delete this agent from the project.
+ {t("agents.dangerZoneDesc", "Permanently delete this agent from the project.")}
@@ -5217,12 +5235,12 @@ function ConfigTab({
onClick={() => void onDelete?.()}
>
- Delete Agent
+ {t("agents.deleteAgent", "Delete Agent")}
{isDeletableState
- ? "Deletion is permanent and cannot be undone."
- : `Agent deletion is only available when state is idle or paused (current state: ${agent.state}).`}
+ ? t("agents.deletionPermanent", "Deletion is permanent and cannot be undone.")
+ : t("agents.deletionNotAvailable", "Agent deletion is only available when state is idle or paused (current state: {{state}}).", { state: agent.state })}
@@ -5252,6 +5270,7 @@ function EmployeesTab({
projectId?: string;
onChildClick?: (childId: string) => void;
}) {
+ const { t } = useTranslation("app");
const [children, setChildren] = useState
([]);
const [isLoading, setIsLoading] = useState(true);
@@ -5266,11 +5285,11 @@ function EmployeesTab({
return (
-
Employees
+ {t("agents.employeesTitle", "Employees")}
- Loading employees...
+ {t("agents.loadingEmployees", "Loading employees...")}
);
@@ -5279,15 +5298,15 @@ function EmployeesTab({
return (
-
Employees
+ {t("agents.employeesTitle", "Employees")}
({children.length})
{children.length === 0 ? (
-
No employees
-
This agent has no employees
+
{t("agents.noEmployees", "No employees")}
+
{t("agents.noEmployeesDesc", "This agent has no employees")}
) : (
diff --git a/packages/dashboard/app/components/AgentErrorDetailsModal.tsx b/packages/dashboard/app/components/AgentErrorDetailsModal.tsx
index c4faaa17cd..91370955cf 100644
--- a/packages/dashboard/app/components/AgentErrorDetailsModal.tsx
+++ b/packages/dashboard/app/components/AgentErrorDetailsModal.tsx
@@ -1,6 +1,7 @@
import "./AgentErrorDetailsModal.css";
import { useMemo, useState } from "react";
import { AlertCircle, Check, Copy, ExternalLink } from "lucide-react";
+import { useTranslation } from "react-i18next";
const DEFAULT_ISSUE_URL = "https://github.com/Runfusion/Fusion/issues/new";
@@ -50,6 +51,7 @@ export function buildAgentErrorIssueUrl(errorText: string, context: AgentErrorIs
export function AgentErrorDetailsModal({ open, onClose, errorText, issueContext }: AgentErrorDetailsModalProps) {
const [copied, setCopied] = useState(false);
+ const { t } = useTranslation("app");
const issueUrl = useMemo(() => buildAgentErrorIssueUrl(errorText, issueContext), [errorText, issueContext]);
if (!open) {
@@ -62,7 +64,7 @@ export function AgentErrorDetailsModal({ open, onClose, errorText, issueContext
- Agent Error Details
+ {t("agentError.title", "Agent Error Details")}
×
@@ -79,10 +81,10 @@ export function AgentErrorDetailsModal({ open, onClose, errorText, issueContext
setTimeout(() => setCopied(false), 1500);
});
}}
- aria-label={copied ? "Copied error to clipboard" : "Copy error to clipboard"}
+ aria-label={copied ? t("agentError.copiedLabel", "Copied error to clipboard") : t("agentError.copyLabel", "Copy error to clipboard")}
>
{copied ?
:
}
- {copied ? "Copied" : "Copy"}
+ {copied ? t("agentError.copied", "Copied") : t("agentError.copy", "Copy")}
- Report on GitHub
+ {t("agentError.reportOnGithub", "Report on GitHub")}
@@ -111,10 +113,11 @@ interface AgentErrorIndicatorProps {
export function AgentErrorIndicator({ errorText, issueContext, summaryPrefix = "Error" }: AgentErrorIndicatorProps) {
const [open, setOpen] = useState(false);
+ const { t } = useTranslation("app");
return (
<>
-
setOpen(true)} aria-label="Open error details">
+ setOpen(true)} aria-label={t("agentError.openDetails", "Open error details")}>
{summaryPrefix}
diff --git a/packages/dashboard/app/components/AgentGenerationModal.tsx b/packages/dashboard/app/components/AgentGenerationModal.tsx
index 770bc4f05d..b98bb05dd6 100644
--- a/packages/dashboard/app/components/AgentGenerationModal.tsx
+++ b/packages/dashboard/app/components/AgentGenerationModal.tsx
@@ -1,4 +1,5 @@
import { useState, useCallback, useEffect, useRef } from "react";
+import { useTranslation } from "react-i18next";
import type { AgentGenerationSpec } from "../api";
import { useMobileScrollLock } from "../hooks/useMobileScrollLock";
import {
@@ -38,6 +39,7 @@ export function AgentGenerationModal({
onGenerated,
projectId,
}: AgentGenerationModalProps) {
+ const { t } = useTranslation("app");
useMobileScrollLock(isOpen);
const [roleDescription, setRoleDescription] = useState("");
const [view, setView] = useState({ type: "input" });
@@ -112,7 +114,7 @@ export function AgentGenerationModal({
err instanceof Error ? err.message : "Failed to generate agent specification";
// Handle rate limit errors with user-friendly message
if (message.includes("429") || message.toLowerCase().includes("rate limit")) {
- setError("Too many requests. Please wait a moment and try again.");
+ setError(t("agents.generation.rateLimited", "Too many requests. Please wait a moment and try again."));
} else {
setError(message);
}
@@ -172,12 +174,12 @@ export function AgentGenerationModal({
✨
- Generate Agent
+ {t("agents.generation.title", "Generate Agent")}
×
@@ -190,18 +192,16 @@ export function AgentGenerationModal({
{view.type === "input" && (
- Describe your agent's role and the AI will generate a complete
- specification including system prompt, suggested configuration, and
- more.
+ {t("agents.generation.info", "Describe your agent's role and the AI will generate a complete specification including system prompt, suggested configuration, and more.")}
-
Role Description
+
{t("agents.generation.roleLabel", "Role Description")}