FN-6770: localize dashboard workflow and task UI strings
Localize dashboard workflow, task, setup, and pull request UI copy through the shared i18n resources. - Replace hardcoded dashboard strings in workflow, task detail/chat/comment, setup wizard, PR, and related modal components with translation lookups. - Add app/common namespace keys across English and secondary locale catalogs, plus regenerated i18n resource types. - Add the published package changeset and clear the now-localized component files from the i18n lint deferral list. Files changed: .changeset/fn-6770-localize-workflow-task-pr.md | 5 + i18next.config.ts | 17 +- packages/dashboard/app/components/Board.tsx | 10 +- .../dashboard/app/components/PrCreateModal.tsx | 38 +-- packages/dashboard/app/components/PrPanel.tsx | 2 +- .../dashboard/app/components/PullRequestView.tsx | 51 ++-- .../dashboard/app/components/SettingsModal.tsx | 92 +++---- .../dashboard/app/components/SetupWizardModal.tsx | 8 +- packages/dashboard/app/components/TaskChatTab.tsx | 107 ++++---- packages/dashboard/app/components/TaskComments.tsx | 2 +- .../dashboard/app/components/TaskDetailModal.tsx | 54 ++-- .../app/components/WorkflowNodeEditor.tsx | 109 +++++---- .../app/components/WorkflowResultsTab.tsx | 190 +++++++------- .../dashboard/app/components/WorkflowSelector.tsx | 20 +- packages/i18n/locales/en/app.json | 268 ++++++++++++++++++-- packages/i18n/locales/en/common.json | 28 +-- packages/i18n/locales/es/app.json | 268 ++++++++++++++++++-- packages/i18n/locales/es/common.json | 28 +-- packages/i18n/locales/fr/app.json | 268 ++++++++++++++++++-- packages/i18n/locales/fr/common.json | 28 +-- packages/i18n/locales/ko/app.json | 268 ++++++++++++++++++-- packages/i18n/locales/ko/common.json | 28 +-- packages/i18n/locales/zh-CN/app.json | 268 ++++++++++++++++++-- packages/i18n/locales/zh-CN/common.json | 28 +-- packages/i18n/locales/zh-TW/app.json | 268 ++++++++++++++++++-- packages/i18n/locales/zh-TW/common.json | 28 +-- packages/i18n/src/resources.d.ts | 272 ++++++++++++++++++--- 27 files changed, 2144 insertions(+), 609 deletions(-) Fusion-Task-Id: FN-6770 Fusion-Task-Lineage: 393b6b8a-1902-4e46-a53e-b4a0793022ca
This commit is contained in:
5
.changeset/fn-6770-localize-workflow-task-pr.md
Normal file
5
.changeset/fn-6770-localize-workflow-task-pr.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"@runfusion/fusion": patch
|
||||
---
|
||||
|
||||
Localized the dashboard workflow/task/setup/PR component cluster and removed the obsolete i18n lint deferrals for those files so the hardcoded-string guardrail scans them again.
|
||||
@@ -6,23 +6,8 @@ import {
|
||||
|
||||
|
||||
const DEFERRED_I18N_LINT_FILES = [
|
||||
// FNXC:i18n-LintBaseline 2026-06-19-00:00:
|
||||
// These exact files still carry pre-existing user-facing copy debt after FN-6749 restored the guardrail scope and token suppression.
|
||||
// Keep the deferral file-scoped and remove entries as those follow-ups localize each cluster.
|
||||
// FNXC:i18n-LintBaseline 2026-06-20-00:00:
|
||||
// FN-6771 localized the settings/sections cluster, so those files are no longer deferred and must stay covered by i18n lint.
|
||||
"packages/dashboard/app/components/WorkflowSelector.tsx",
|
||||
"packages/dashboard/app/components/WorkflowResultsTab.tsx",
|
||||
"packages/dashboard/app/components/WorkflowNodeEditor.tsx",
|
||||
"packages/dashboard/app/components/TaskDetailModal.tsx",
|
||||
"packages/dashboard/app/components/TaskComments.tsx",
|
||||
"packages/dashboard/app/components/TaskChatTab.tsx",
|
||||
"packages/dashboard/app/components/SetupWizardModal.tsx",
|
||||
"packages/dashboard/app/components/SettingsModal.tsx",
|
||||
"packages/dashboard/app/components/PullRequestView.tsx",
|
||||
"packages/dashboard/app/components/PrPanel.tsx",
|
||||
"packages/dashboard/app/components/PrCreateModal.tsx",
|
||||
"packages/dashboard/app/components/Board.tsx",
|
||||
// FN-6770 and FN-6771 localized the remaining workflow/task/setup/PR and settings/sections clusters, so no dashboard component files remain deferred from hardcoded-string lint.
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Column } from "./Column";
|
||||
import "./Lane.css";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { useState, useMemo, useEffect, useCallback, useRef } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Pencil, Plus } from "lucide-react";
|
||||
import { fetchWorkflowSteps, fetchBoardWorkflows, promoteTask, type ModelInfo, type BoardWorkflowDefinition, type BoardWorkflowsPayload } from "../api";
|
||||
import { useBlockerFanout } from "../hooks/useBlockerFanout";
|
||||
@@ -124,6 +125,7 @@ function areWorkflowNameLookupsEqual(previous: ReadonlyMap<string, string>, next
|
||||
}
|
||||
|
||||
export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onPauseTask, onOpenDetail, onOpenGroupModal, addToast, onQuickCreate, onNewTask, autoMerge, onToggleAutoMerge, globalPaused, onUpdateTask, onRetryTask, onArchiveTask, onUnarchiveTask, onDeleteTask, onArchiveAllDone, onLoadArchivedTasks, searchQuery = "", availableModels, onPlanningMode, onSubtaskBreakdown, onOpenDetailWithTab, favoriteProviders, favoriteModels, onToggleFavorite, onToggleModelFavorite, taskStuckTimeoutMs, onOpenMission, staleHighFanoutBlockerAgeThresholdMs, lastFetchTimeMs, prAuthAvailable, onOpenWorkflowEditor, onCreateWorkflow }: BoardProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const [archivedCollapsed, setArchivedCollapsed] = useState(true);
|
||||
const archivedLoadedRef = useRef(false);
|
||||
const [workflowStepNameLookup, setWorkflowStepNameLookup] = useState<ReadonlyMap<string, string>>(EMPTY_WORKFLOW_STEP_NAME_LOOKUP);
|
||||
@@ -527,8 +529,8 @@ export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onPauseTask
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm board-workflow-edit-btn"
|
||||
onClick={() => onOpenWorkflowEditor(selectedWorkflow.id)}
|
||||
title="Edit workflows"
|
||||
aria-label="Edit workflows"
|
||||
title={t("board.workflow.edit", "Edit workflows")}
|
||||
aria-label={t("board.workflow.edit", "Edit workflows")}
|
||||
>
|
||||
<Pencil size={15} />
|
||||
</button>
|
||||
@@ -538,8 +540,8 @@ export function Board({ tasks, projectId, maxConcurrent, onMoveTask, onPauseTask
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm board-workflow-create-btn"
|
||||
onClick={onCreateWorkflow}
|
||||
title="New workflow"
|
||||
aria-label="New workflow"
|
||||
title={t("board.workflow.new", "New workflow")}
|
||||
aria-label={t("board.workflow.new", "New workflow")}
|
||||
>
|
||||
<Plus size={15} />
|
||||
</button>
|
||||
|
||||
@@ -488,7 +488,7 @@ export function PrCreateModal({
|
||||
<>
|
||||
<section className="pr-create-modal__section">
|
||||
<h3 className="pr-create-modal__section-title">{t("pr.preflightChecks", "Pre-flight checks")}</h3>
|
||||
{preflightLoading ? <div className="pr-create-modal__loading pr-create-modal__section-loading"><span className="status-dot status-dot--pending" aria-hidden="true" />Loading pre-flight checks…</div> : null}
|
||||
{preflightLoading ? <div className="pr-create-modal__loading pr-create-modal__section-loading"><span className="status-dot status-dot--pending" aria-hidden="true" />{t("pr.loadingPreflight", "Loading pre-flight checks…")}</div> : null}
|
||||
{preflightError ? <div className="form-error pr-error" role="alert"><p>{preflightError}</p></div> : null}
|
||||
{!preflightLoading && !preflightError ? (
|
||||
<>
|
||||
@@ -511,8 +511,8 @@ export function PrCreateModal({
|
||||
{!preflight?.branchOnRemote ? (
|
||||
<div className="card pr-create-modal__preflight-remediation">
|
||||
<div className="pr-create-modal__conflict-copy">
|
||||
<p className="pr-create-modal__conflict-title">Push branch to remote</p>
|
||||
<p className="pr-create-modal__conflict-message">Fusion will push this task's branch to origin so the PR can be created.</p>
|
||||
<p className="pr-create-modal__conflict-title">{t("pr.pushBranch.title", "Push branch to remote")}</p>
|
||||
<p className="pr-create-modal__conflict-message">{t("pr.pushBranch.message", "Fusion will push this task's branch to origin so the PR can be created.")}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -521,15 +521,15 @@ export function PrCreateModal({
|
||||
disabled={pushingBranch || preflightLoading || !baseBranch}
|
||||
>
|
||||
{pushingBranch ? <RefreshCw size={14} className="spin" /> : null}
|
||||
Push branch to remote
|
||||
{t("pr.pushBranch.button", "Push branch to remote")}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
{preflight?.conflictsWithBase ? (
|
||||
<div className="card pr-create-modal__conflict-resolution">
|
||||
<div className="pr-create-modal__conflict-copy">
|
||||
<p className="pr-create-modal__conflict-title">Resolve conflicts with AI</p>
|
||||
<p className="pr-create-modal__conflict-message">Fusion will use AI to resolve conflicts on this branch and push it.</p>
|
||||
<p className="pr-create-modal__conflict-title">{t("pr.resolveConflicts.title", "Resolve conflicts with AI")}</p>
|
||||
<p className="pr-create-modal__conflict-message">{t("pr.resolveConflicts.message", "Fusion will use AI to resolve conflicts on this branch and push it.")}</p>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
@@ -538,7 +538,7 @@ export function PrCreateModal({
|
||||
disabled={resolvingConflicts || preflightLoading || !baseBranch}
|
||||
>
|
||||
{resolvingConflicts ? <RefreshCw size={14} className="spin" /> : null}
|
||||
Resolve conflicts with AI
|
||||
{t("pr.resolveConflicts.button", "Resolve conflicts with AI")}
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -554,7 +554,7 @@ export function PrCreateModal({
|
||||
{userEditedTitle && <button type="button" className="btn btn-sm" onClick={() => { setTitle(aiTitle); setUserEditedTitle(false); }}>{t("pr.revertToAi", "Revert to AI version")}</button>}
|
||||
</div>
|
||||
</div>
|
||||
{metadataLoading ? <div className="pr-create-modal__loading pr-create-modal__section-loading"><span className="status-dot status-dot--pending" aria-hidden="true" />Generating AI title…</div> : null}
|
||||
{metadataLoading ? <div className="pr-create-modal__loading pr-create-modal__section-loading"><span className="status-dot status-dot--pending" aria-hidden="true" />{t("pr.generatingTitle", "Generating AI title…")}</div> : null}
|
||||
{metadataError ? <div className="form-error pr-error" role="alert"><p>{metadataError}</p></div> : null}
|
||||
<input id="pr-create-modal-title" className="input" value={title} onChange={(event) => { setTitle(event.target.value); setUserEditedTitle(true); }} />
|
||||
</section>
|
||||
@@ -567,7 +567,7 @@ export function PrCreateModal({
|
||||
{userEditedBody && <button type="button" className="btn btn-sm" onClick={() => { setBody(aiBody); setUserEditedBody(false); }}>{t("pr.revertToAi", "Revert to AI version")}</button>}
|
||||
</div>
|
||||
</div>
|
||||
{metadataLoading ? <div className="pr-create-modal__loading pr-create-modal__section-loading"><span className="status-dot status-dot--pending" aria-hidden="true" />Generating AI body…</div> : null}
|
||||
{metadataLoading ? <div className="pr-create-modal__loading pr-create-modal__section-loading"><span className="status-dot status-dot--pending" aria-hidden="true" />{t("pr.generatingBody", "Generating AI body…")}</div> : null}
|
||||
<textarea id="pr-create-modal-body" className="input pr-create-modal__body-input" value={body} onChange={(event) => { setBody(event.target.value); setUserEditedBody(true); }} rows={8} />
|
||||
{templateUsed && <p className="pr-create-template-hint">{t("pr.usingTemplate", "Using <code>.github/pull_request_template.md</code>")}</p>}
|
||||
</section>
|
||||
@@ -575,7 +575,7 @@ export function PrCreateModal({
|
||||
<section className="pr-create-modal__section pr-create-modal__grid-two">
|
||||
<div>
|
||||
<label className="pr-create-modal__label" htmlFor="pr-create-modal-base">{t("pr.baseBranch", "Base branch")}</label>
|
||||
{optionsLoading ? <div className="pr-create-modal__loading pr-create-modal__section-loading"><span className="status-dot status-dot--pending" aria-hidden="true" />Loading PR options…</div> : null}
|
||||
{optionsLoading ? <div className="pr-create-modal__loading pr-create-modal__section-loading"><span className="status-dot status-dot--pending" aria-hidden="true" />{t("pr.loadingOptions", "Loading PR options…")}</div> : null}
|
||||
{optionsError ? <div className="form-error pr-error" role="alert"><p>{optionsError}</p></div> : null}
|
||||
<select id="pr-create-modal-base" className="select" value={baseBranch} onChange={(event) => void handleBaseChange(event.target.value)} disabled={optionsLoading || Boolean(optionsError) || (options?.baseBranches?.length ?? 0) === 0}>
|
||||
{(options?.baseBranches ?? []).map((branch) => <option key={branch} value={branch}>{branch}</option>)}
|
||||
@@ -588,7 +588,7 @@ export function PrCreateModal({
|
||||
</section>
|
||||
|
||||
<OptionChips
|
||||
label="Reviewers"
|
||||
label={t("pr.reviewers", "Reviewers")}
|
||||
options={options?.reviewers ?? []}
|
||||
selected={reviewers}
|
||||
onChange={setReviewers}
|
||||
@@ -596,7 +596,7 @@ export function PrCreateModal({
|
||||
getLabel={(option) => option.name ? `${option.name} (@${option.login})` : `@${option.login}`}
|
||||
/>
|
||||
<OptionChips
|
||||
label="Assignees"
|
||||
label={t("pr.assignees", "Assignees")}
|
||||
options={options?.assignees ?? []}
|
||||
selected={assignees}
|
||||
onChange={setAssignees}
|
||||
@@ -604,7 +604,7 @@ export function PrCreateModal({
|
||||
getLabel={(option) => option.name ? `${option.name} (@${option.login})` : `@${option.login}`}
|
||||
/>
|
||||
<OptionChips
|
||||
label="Labels"
|
||||
label={t("pr.labels", "Labels")}
|
||||
options={options?.labels ?? []}
|
||||
selected={labels}
|
||||
onChange={setLabels}
|
||||
@@ -617,7 +617,7 @@ export function PrCreateModal({
|
||||
<summary>{t("pr.previewTitle", "Diff & commit preview")}</summary>
|
||||
<div className="pr-create-modal__preview">
|
||||
<h4>{t("pr.commitsLabel", "Commits")}</h4>
|
||||
{!preflightLoading && !preflightError && (preflight?.commits?.length ?? 0) === 0 ? <p className="pr-create-template-hint">No commits found.</p> : null}
|
||||
{!preflightLoading && !preflightError && (preflight?.commits?.length ?? 0) === 0 ? <p className="pr-create-template-hint">{t("pr.noCommits", "No commits found.")}</p> : null}
|
||||
{(preflight?.commits ?? []).map((commit) => (
|
||||
<div className="pr-create-modal__commit-row" key={commit.sha}>
|
||||
<code>{commit.sha.slice(0, 7)}</code>
|
||||
@@ -626,7 +626,7 @@ export function PrCreateModal({
|
||||
</div>
|
||||
))}
|
||||
<h4>{t("pr.changedFilesLabel", "Changed files")}</h4>
|
||||
{!preflightLoading && !preflightError && (preflight?.changedFiles?.length ?? 0) === 0 ? <p className="pr-create-template-hint">No changed files detected.</p> : null}
|
||||
{!preflightLoading && !preflightError && (preflight?.changedFiles?.length ?? 0) === 0 ? <p className="pr-create-template-hint">{t("pr.noChangedFiles", "No changed files detected.")}</p> : null}
|
||||
{(preflight?.changedFiles ?? []).map((file) => (
|
||||
<div className="pr-create-modal__file-row" key={file.path}>
|
||||
<span>{file.path}</span>
|
||||
@@ -641,7 +641,7 @@ export function PrCreateModal({
|
||||
<div className="form-error pr-error" role="alert">
|
||||
<p>{pushBranchError}</p>
|
||||
<div className="pr-error__actions">
|
||||
<button type="button" className="btn btn-sm pr-error__dismiss" onClick={() => setPushBranchError(null)} aria-label="Dismiss push branch error">×</button>
|
||||
<button type="button" className="btn btn-sm pr-error__dismiss" onClick={() => setPushBranchError(null)} aria-label={t("pr.dismissPushBranchError", "Dismiss push branch error")}>×</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -650,7 +650,7 @@ export function PrCreateModal({
|
||||
<div className="form-error pr-error" role="alert">
|
||||
<p>{resolveConflictError}</p>
|
||||
<div className="pr-error__actions">
|
||||
<button type="button" className="btn btn-sm pr-error__dismiss" onClick={() => setResolveConflictError(null)} aria-label="Dismiss conflict resolution error">×</button>
|
||||
<button type="button" className="btn btn-sm pr-error__dismiss" onClick={() => setResolveConflictError(null)} aria-label={t("pr.dismissConflictResolutionError", "Dismiss conflict resolution error")}>×</button>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -660,8 +660,8 @@ export function PrCreateModal({
|
||||
<p>{submitError}</p>
|
||||
{lastGhError?.hint ? <p className="pr-error__hint">{lastGhError.hint}</p> : null}
|
||||
<div className="pr-error__actions">
|
||||
{lastGhError?.action?.kind === "shell" ? <p>Action: run <code>{lastGhError.action.command}</code></p> : null}
|
||||
{lastGhError?.action?.kind === "open" ? <p>Action: open <a href={lastGhError.action.url} target="_blank" rel="noreferrer">docs</a></p> : null}
|
||||
{lastGhError?.action?.kind === "shell" ? <p>{t("pr.error.actionRun", "Action: run")} <code>{lastGhError.action.command}</code></p> : null}
|
||||
{lastGhError?.action?.kind === "open" ? <p>{t("pr.error.actionOpen", "Action: open")} <a href={lastGhError.action.url} target="_blank" rel="noreferrer">{t("pr.error.docs", "docs")}</a></p> : null}
|
||||
{lastGhError?.retryable ? <button type="button" className="btn btn-sm pr-error__retry" onClick={() => void submit()}>{t("actions.retry", "Retry")}</button> : null}
|
||||
<button type="button" className="btn btn-sm pr-error__dismiss" onClick={() => { setLastGhError(null); setSubmitError(null); }} aria-label={t("pr.dismissError", "Dismiss PR error")}>×</button>
|
||||
</div>
|
||||
|
||||
@@ -238,7 +238,7 @@ function PrCard({
|
||||
<div>{lastGhError.message}</div>
|
||||
{lastGhError.hint ? <div className="pr-error__hint">{lastGhError.hint}</div> : null}
|
||||
<div className="pr-error__actions">
|
||||
{lastGhError.action?.kind === "shell" ? <div>Action: run <code>{lastGhError.action.command}</code></div> : null}
|
||||
{lastGhError.action?.kind === "shell" ? <div>{t("pr.error.actionRun", "Action: run")} <code>{lastGhError.action.command}</code></div> : null}
|
||||
{lastGhError.retryable ? <button className="btn btn-sm pr-error__retry" onClick={() => void handleRefresh()}>{t("git.retryButton", "Retry")}</button> : null}
|
||||
<button className="btn btn-sm pr-error__dismiss" onClick={() => setLastGhError(null)} aria-label={t("git.dismissPrError", "Dismiss PR error")}>×</button>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import {
|
||||
GitPullRequest,
|
||||
GitMerge,
|
||||
@@ -101,6 +102,7 @@ function ChecksIcon({ rollup }: { rollup: string }) {
|
||||
}
|
||||
|
||||
export function PullRequestView(props: PullRequestViewProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const { detail: detailProp, pullRequestId, projectId, onAction, loadPullRequest } = props;
|
||||
const [detail, setDetail] = useState<PrDetail | null>(detailProp ?? null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
@@ -166,7 +168,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
if (!detail) {
|
||||
return (
|
||||
<div className="pr-view pr-view--loading" data-testid="pr-view-loading">
|
||||
Loading PR…
|
||||
{t("pr.view.loading", "Loading PR…")}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -179,7 +181,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
<div className="pr-view" data-testid="pr-view" data-state="creating">
|
||||
<PrIdentityHeader detail={detail} />
|
||||
<div className="pr-placeholder" data-testid="pr-creating">
|
||||
<Clock size={16} /> Creating PR…
|
||||
<Clock size={16} /> {t("pr.view.creating", "Creating PR…")}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -192,7 +194,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
<PrIdentityHeader detail={detail} />
|
||||
<div className="pr-error-reason" data-testid="pr-failed">
|
||||
<AlertTriangle size={16} className="pr-icon-failure" />
|
||||
<span>{detail.failureReason ?? "PR creation failed"}</span>
|
||||
<span>{detail.failureReason ?? t("pr.view.creationFailed", "PR creation failed")}</span>
|
||||
</div>
|
||||
<div className="pr-action-bar">
|
||||
<button
|
||||
@@ -202,7 +204,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
disabled={busy === "retry-create"}
|
||||
onClick={() => void runAction("retry-create")}
|
||||
>
|
||||
<RotateCcw size={14} /> Retry PR creation
|
||||
<RotateCcw size={14} /> {t("pr.view.retryCreation", "Retry PR creation")}
|
||||
</button>
|
||||
</div>
|
||||
{error && <div className="pr-inline-error">{error}</div>}
|
||||
@@ -216,7 +218,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
<div className="pr-view" data-testid="pr-view" data-state="unverified">
|
||||
<PrIdentityHeader detail={detail} />
|
||||
<div className="pr-notice pr-notice--unverified" data-testid="pr-unverified">
|
||||
<Clock size={16} /> Verifying with GitHub…
|
||||
<Clock size={16} /> {t("pr.view.verifyingGithub", "Verifying with GitHub…")}
|
||||
</div>
|
||||
<div className="pr-action-bar">
|
||||
<button
|
||||
@@ -224,9 +226,9 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
className="pr-action"
|
||||
data-testid="pr-merge"
|
||||
disabled
|
||||
title="Merge is disabled until GitHub verifies this PR"
|
||||
title={t("pr.view.mergeDisabledUntilVerified", "Merge is disabled until GitHub verifies this PR")}
|
||||
>
|
||||
<GitMerge size={14} /> Merge
|
||||
<GitMerge size={14} /> {t("pr.view.merge", "Merge")}
|
||||
</button>
|
||||
</div>
|
||||
{/* checks/threads hidden while unverified */}
|
||||
@@ -243,8 +245,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
{/* responding banner */}
|
||||
{state === "responding" && (
|
||||
<div className="pr-banner pr-banner--responding" data-testid="pr-responding">
|
||||
<MessageSquare size={16} /> Response run in progress — {summary.pendingThreads} threads
|
||||
pending
|
||||
<MessageSquare size={16} /> {t("pr.view.responsePending", "Response run in progress — {{count}} threads pending", { count: summary.pendingThreads })}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -257,17 +258,17 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
disabled={state === "responding" || busy === "approve"}
|
||||
onClick={() => void runAction("approve")}
|
||||
>
|
||||
<ThumbsUp size={14} /> Approve
|
||||
<ThumbsUp size={14} /> {t("pr.view.approve", "Approve")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="pr-action pr-action--retry"
|
||||
data-testid="pr-retry"
|
||||
disabled={state === "responding" || busy === "retry"}
|
||||
title={state === "responding" ? "A response run is already in progress" : undefined}
|
||||
title={state === "responding" ? t("pr.view.responseAlreadyInProgress", "A response run is already in progress") : undefined}
|
||||
onClick={() => void runAction("retry")}
|
||||
>
|
||||
<RotateCcw size={14} /> Request retry
|
||||
<RotateCcw size={14} /> {t("pr.view.requestRetry", "Request retry")}
|
||||
</button>
|
||||
{!confirmingMerge ? (
|
||||
<button
|
||||
@@ -275,10 +276,10 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
className="pr-action pr-action--merge"
|
||||
data-testid="pr-merge"
|
||||
disabled={conflicting || state === "responding" || busy === "merge"}
|
||||
title={conflicting ? "Resolve conflicts on GitHub before merging" : undefined}
|
||||
title={conflicting ? t("pr.view.resolveConflictsBeforeMerge", "Resolve conflicts on GitHub before merging") : undefined}
|
||||
onClick={() => setConfirmingMerge(true)}
|
||||
>
|
||||
<GitMerge size={14} /> Merge
|
||||
<GitMerge size={14} /> {t("pr.view.merge", "Merge")}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
@@ -288,7 +289,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
disabled={busy === "merge"}
|
||||
onClick={() => void runAction("merge")}
|
||||
>
|
||||
<GitMerge size={14} /> Confirm merge
|
||||
<GitMerge size={14} /> {t("pr.view.confirmMerge", "Confirm merge")}
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
@@ -298,7 +299,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
disabled={busy === "close"}
|
||||
onClick={() => void runAction("close")}
|
||||
>
|
||||
<XCircle size={14} /> Close
|
||||
<XCircle size={14} /> {t("pr.view.close", "Close")}
|
||||
</button>
|
||||
|
||||
<label className="pr-automerge-toggle" data-testid="pr-automerge">
|
||||
@@ -308,7 +309,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
disabled={busy === "automerge"}
|
||||
onChange={(e) => void runAction("automerge", { enabled: e.target.checked })}
|
||||
/>
|
||||
<span>Auto-merge</span>
|
||||
<span>{t("pr.view.autoMerge", "Auto-merge")}</span>
|
||||
<span className="pr-automerge-gate" data-testid="pr-automerge-gate">
|
||||
{summary.autoMergeReason}
|
||||
</span>
|
||||
@@ -324,17 +325,17 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Resolve conflicts on GitHub <ExternalLink size={12} />
|
||||
{t("pr.view.resolveConflictsOnGithub", "Resolve conflicts on GitHub")} <ExternalLink size={12} />
|
||||
</a>
|
||||
)}
|
||||
|
||||
{/* ── merge-readiness summary ─────────────────────────────────────── */}
|
||||
<div className="pr-summary" data-testid="pr-summary">
|
||||
<span className="pr-summary-item" data-testid="pr-summary-mergeable">
|
||||
Mergeable: {summary.mergeable}
|
||||
{t("pr.view.mergeableLabel", "Mergeable:")} {summary.mergeable}
|
||||
</span>
|
||||
<span className="pr-summary-item" data-testid="pr-summary-review">
|
||||
Review: {summary.reviewDecision ?? "none"}
|
||||
{t("pr.view.reviewLabel", "Review:")} {summary.reviewDecision ?? t("pr.view.none", "none")}
|
||||
</span>
|
||||
<span className="pr-summary-item" data-testid="pr-summary-checks">
|
||||
<ChecksIcon rollup={summary.checksRollup} /> {summary.checksRollup}
|
||||
@@ -344,7 +345,7 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
{/* ── threads (agent replies nested) ───────────────────────────────── */}
|
||||
<div className="pr-threads" data-testid="pr-threads">
|
||||
{detail.threads.length === 0 ? (
|
||||
<div className="pr-threads-empty">No review threads.</div>
|
||||
<div className="pr-threads-empty">{t("pr.view.noReviewThreads", "No review threads.")}</div>
|
||||
) : (
|
||||
detail.threads.map((thread) => (
|
||||
<div
|
||||
@@ -358,24 +359,24 @@ export function PullRequestView(props: PullRequestViewProps) {
|
||||
<div className="pr-thread-head">
|
||||
{thread.outcome === "pending" && (
|
||||
<span className="pr-thread-pending">
|
||||
<Clock size={12} /> pending
|
||||
<Clock size={12} /> {t("pr.view.threadPending", "pending")}
|
||||
</span>
|
||||
)}
|
||||
{thread.outcome === "disagreed" && (
|
||||
<span className="pr-thread-disagreed">
|
||||
<AlertTriangle size={12} /> agent disagreed
|
||||
<AlertTriangle size={12} /> {t("pr.view.agentDisagreed", "agent disagreed")}
|
||||
</span>
|
||||
)}
|
||||
{thread.outcome === "fixed" && (
|
||||
<span className="pr-thread-fixed">
|
||||
<CheckCircle size={12} /> fixed
|
||||
<CheckCircle size={12} /> {t("pr.view.threadFixed", "fixed")}
|
||||
</span>
|
||||
)}
|
||||
<span className="pr-thread-id">{thread.threadId}</span>
|
||||
</div>
|
||||
{thread.fixCommitSha && (
|
||||
<div className="pr-thread-reply" data-testid="pr-thread-reply">
|
||||
Agent reply — fix {thread.fixCommitSha.slice(0, 8)}
|
||||
{t("pr.view.agentReplyFix", "Agent reply — fix {{sha}}", { sha: thread.fixCommitSha.slice(0, 8) })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1850,10 +1850,10 @@ export function SettingsModal({
|
||||
const info = await fetchBackups(projectId);
|
||||
setBackupInfo(info);
|
||||
} else {
|
||||
addToast(result.error || "Failed to create backup", "error");
|
||||
addToast(result.error || t("settings.backups.createFailed", "Failed to create backup"), "error");
|
||||
}
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err) || "Failed to create backup", "error");
|
||||
addToast(getErrorMessage(err) || t("settings.backups.createFailed", "Failed to create backup"), "error");
|
||||
} finally {
|
||||
setBackupLoading(false);
|
||||
}
|
||||
@@ -1879,10 +1879,14 @@ export function SettingsModal({
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
|
||||
const scopeLabel = scope === "global" ? "global" : scope === "project" ? "project" : "all";
|
||||
addToast(`Settings exported (${scopeLabel} scope)`, "success");
|
||||
const scopeLabel = scope === "global"
|
||||
? t("settings.importExport.scopeLabel.global", "global")
|
||||
: scope === "project"
|
||||
? t("settings.importExport.scopeLabel.project", "project")
|
||||
: t("settings.importExport.scopeLabel.all", "all");
|
||||
addToast(t("settings.importExport.exported", "Settings exported ({{scope}} scope)", { scope: scopeLabel }), "success");
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err) || "Failed to export settings", "error");
|
||||
addToast(getErrorMessage(err) || t("settings.importExport.exportFailed", "Failed to export settings"), "error");
|
||||
}
|
||||
}, [addToast, activeSectionScope, projectId]);
|
||||
|
||||
@@ -1899,7 +1903,7 @@ export function SettingsModal({
|
||||
setImportPreview(data);
|
||||
setImportDialogOpen(true);
|
||||
} catch (err) {
|
||||
addToast(`Invalid JSON file: ${getErrorMessage(err)}`, "error");
|
||||
addToast(t("settings.importExport.invalidJson", "Invalid JSON file: {{error}}", { error: getErrorMessage(err) }), "error");
|
||||
setImportFile(null);
|
||||
} finally {
|
||||
setImportLoading(false);
|
||||
@@ -1914,10 +1918,10 @@ export function SettingsModal({
|
||||
const result = await importSettings(importPreview, { scope: importScope, merge: importMerge }, projectId);
|
||||
if (result.success) {
|
||||
const parts: string[] = [];
|
||||
if (result.globalCount > 0) parts.push(`${result.globalCount} global`);
|
||||
if (result.projectCount > 0) parts.push(`${result.projectCount} project`);
|
||||
if (result.workflowSettingsCount > 0) parts.push(`${result.workflowSettingsCount} workflow setting value(s)`);
|
||||
addToast(`Imported ${parts.join(", ")} setting(s)`, "success");
|
||||
if (result.globalCount > 0) parts.push(t("settings.importExport.counts.global", "{{count}} global", { count: result.globalCount }));
|
||||
if (result.projectCount > 0) parts.push(t("settings.importExport.counts.project", "{{count}} project", { count: result.projectCount }));
|
||||
if (result.workflowSettingsCount > 0) parts.push(t("settings.importExport.counts.workflowSettings", "{{count}} workflow setting value", { count: result.workflowSettingsCount }));
|
||||
addToast(t("settings.importExport.imported", "Imported {{counts}} setting(s)", { counts: parts.join(", ") }), "success");
|
||||
setImportDialogOpen(false);
|
||||
setImportPreview(null);
|
||||
setImportFile(null);
|
||||
@@ -1925,10 +1929,10 @@ export function SettingsModal({
|
||||
const refreshed = await fetchSettings(projectId);
|
||||
setForm(refreshed);
|
||||
} else {
|
||||
addToast(result.error || "Import failed", "error");
|
||||
addToast(result.error || t("settings.importExport.importFailed", "Import failed"), "error");
|
||||
}
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err) || "Failed to import settings", "error");
|
||||
addToast(getErrorMessage(err) || t("settings.importExport.importFailedDetailed", "Failed to import settings"), "error");
|
||||
} finally {
|
||||
setImportLoading(false);
|
||||
}
|
||||
@@ -2373,11 +2377,11 @@ export function SettingsModal({
|
||||
const result = await installQmd(projectId);
|
||||
await refreshMemoryBackend();
|
||||
addToast(
|
||||
result.qmdAvailable ? "qmd installed successfully" : "qmd install finished, but qmd is still unavailable",
|
||||
result.qmdAvailable ? t("settings.memory.qmdInstalled", "qmd installed successfully") : t("settings.memory.qmdInstallUnavailable", "qmd install finished, but qmd is still unavailable"),
|
||||
result.qmdAvailable ? "success" : "warning",
|
||||
);
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err) || "Failed to install qmd", "error");
|
||||
addToast(getErrorMessage(err) || t("settings.memory.qmdInstallFailed", "Failed to install qmd"), "error");
|
||||
} finally {
|
||||
setQmdInstallLoading(false);
|
||||
}
|
||||
@@ -2471,14 +2475,14 @@ export function SettingsModal({
|
||||
try {
|
||||
const result = await installCloudflared(projectId);
|
||||
if (!result.success) {
|
||||
setCloudflaredInstallError(result.error ?? "Installation failed");
|
||||
setCloudflaredInstallError(result.error ?? t("settings.remote.installationFailed", "Installation failed"));
|
||||
return;
|
||||
}
|
||||
const status = await fetchRemoteStatus(projectId);
|
||||
setRemoteStatus(status);
|
||||
addToast(t("settings.remote.cloudflaredInstalled", "cloudflared installed successfully"), "success");
|
||||
} catch (err) {
|
||||
setCloudflaredInstallError(err instanceof Error ? err.message : "Installation failed");
|
||||
setCloudflaredInstallError(err instanceof Error ? err.message : t("settings.remote.installationFailed", "Installation failed"));
|
||||
} finally {
|
||||
setCloudflaredInstalling(false);
|
||||
}
|
||||
@@ -2871,15 +2875,15 @@ export function SettingsModal({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="settings-github-star-btn"
|
||||
aria-label="Star Fusion on GitHub"
|
||||
title="Star Fusion on GitHub"
|
||||
aria-label={t("settings.header.starFusion", "Star Fusion on GitHub")}
|
||||
title={t("settings.header.starFusion", "Star Fusion on GitHub")}
|
||||
onClick={markStarClicked}
|
||||
data-clicked={starClicked ? "true" : "false"}
|
||||
>
|
||||
<span className="settings-github-star-btn__action">
|
||||
<ProviderIcon provider="github" size="sm" />
|
||||
<Star size={11} aria-hidden="true" />
|
||||
Star
|
||||
{t("settings.header.star", "Star")}
|
||||
</span>
|
||||
{gitHubStarCount !== null && (
|
||||
<span className="settings-github-star-btn__count" aria-label={`${gitHubStarCount.toLocaleString()} stars`}>
|
||||
@@ -2894,14 +2898,14 @@ export function SettingsModal({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn btn-sm settings-header-discord-btn"
|
||||
aria-label="Join our Discord"
|
||||
title="Join our Discord"
|
||||
aria-label={t("settings.header.joinDiscord", "Join our Discord")}
|
||||
title={t("settings.header.joinDiscord", "Join our Discord")}
|
||||
>
|
||||
<DiscordIcon size={13} />
|
||||
{t("settings.header.discord", "Discord")}
|
||||
</a>
|
||||
</div>
|
||||
<button className="modal-close" onClick={onClose} aria-label="Close">
|
||||
<button className="modal-close" onClick={onClose} aria-label={t("actions.close", "Close")}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
@@ -2971,8 +2975,8 @@ export function SettingsModal({
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="btn btn-sm settings-footer-help-btn"
|
||||
aria-label="Help and discussions"
|
||||
title="Help and discussions"
|
||||
aria-label={t("settings.footer.helpDiscussions", "Help and discussions")}
|
||||
title={t("settings.footer.helpDiscussions", "Help and discussions")}
|
||||
>
|
||||
<HelpCircle size={13} aria-hidden="true" />
|
||||
{t("settings.footer.help", "Help")}
|
||||
@@ -2986,8 +2990,8 @@ export function SettingsModal({
|
||||
void handleCheckForUpdates();
|
||||
}}
|
||||
disabled={updateCheckLoading}
|
||||
aria-label="Check for updates"
|
||||
title="Check for updates"
|
||||
aria-label={t("settings.footer.checkUpdates", "Check for updates")}
|
||||
title={t("settings.footer.checkUpdates", "Check for updates")}
|
||||
>
|
||||
<span className="settings-modal-version">{t("settings.footer.version", "Version {{version}}", { version: appVersion })}</span>
|
||||
<RefreshCw size={12} className={updateCheckLoading ? "spinning" : undefined} />
|
||||
@@ -3030,7 +3034,7 @@ export function SettingsModal({
|
||||
className="btn btn-sm"
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
disabled={importLoading}
|
||||
title="Import settings from JSON file"
|
||||
title={t("settings.importExport.importTitleAttr", "Import settings from JSON file")}
|
||||
>
|
||||
{importLoading ? t("settings.importExport.loadingFile", "Loading…") : t("settings.importExport.importBtn", "Import")}
|
||||
</button>
|
||||
@@ -3052,18 +3056,18 @@ export function SettingsModal({
|
||||
onClick={handleOverlapPathPickerOverlayClick}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Browse workspace path"
|
||||
aria-label={t("settings.scheduling.browseWorkspacePath", "Browse workspace path")}
|
||||
>
|
||||
<div className="modal modal-lg settings-overlap-path-picker-modal" onClick={(event) => event.stopPropagation()}>
|
||||
<div className="modal-header">
|
||||
<h3>{t("settings.scheduling.selectIgnoredOverlapPath", "Select ignored overlap path")}</h3>
|
||||
<button className="modal-close" onClick={closeOverlapPathPicker} aria-label="Close">
|
||||
<button className="modal-close" onClick={closeOverlapPathPicker} aria-label={t("actions.close", "Close")}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body settings-overlap-path-picker-body">
|
||||
<p className="settings-overlap-path-picker-note">
|
||||
Choose a file to ignore directly, or navigate into a folder and select the current directory.
|
||||
{t("settings.scheduling.overlapPickerNote", "Choose a file to ignore directly, or navigate into a folder and select the current directory.")}
|
||||
</p>
|
||||
<FileBrowser
|
||||
entries={overlapPathPickerEntries}
|
||||
@@ -3080,7 +3084,7 @@ export function SettingsModal({
|
||||
<div className="modal-actions">
|
||||
<div className="modal-actions-left">
|
||||
<small>
|
||||
Current directory: <code>{overlapPathPickerCurrentPath === "." ? "(project root)" : overlapPathPickerCurrentPath}</code>
|
||||
{t("settings.fileBrowser.currentDirectory", "Current directory:")} <code>{overlapPathPickerCurrentPath === "." ? t("settings.fileBrowser.projectRoot", "(project root)") : overlapPathPickerCurrentPath}</code>
|
||||
</small>
|
||||
</div>
|
||||
<div className="modal-actions-right">
|
||||
@@ -3106,18 +3110,18 @@ export function SettingsModal({
|
||||
onClick={handleWorktreesDirPickerOverlayClick}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-label="Browse worktrees directory"
|
||||
aria-label={t("settings.worktrees.browseWorktreesDirectory", "Browse worktrees directory")}
|
||||
>
|
||||
<div className="modal modal-lg settings-overlap-path-picker-modal" onClick={(event) => event.stopPropagation()}>
|
||||
<div className="modal-header">
|
||||
<h3>{t("settings.worktrees.selectWorktreesDir", "Select worktrees directory")}</h3>
|
||||
<button className="modal-close" onClick={closeWorktreesDirPicker} aria-label="Close">
|
||||
<button className="modal-close" onClick={closeWorktreesDirPicker} aria-label={t("actions.close", "Close")}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
<div className="modal-body settings-overlap-path-picker-body">
|
||||
<p className="settings-overlap-path-picker-note">
|
||||
Navigate to the folder where Fusion should create task worktrees, then select the current directory.
|
||||
{t("settings.worktrees.worktreesPickerNote", "Navigate to the folder where Fusion should create task worktrees, then select the current directory.")}
|
||||
</p>
|
||||
<FileBrowser
|
||||
entries={worktreesDirPickerEntries}
|
||||
@@ -3134,7 +3138,7 @@ export function SettingsModal({
|
||||
<div className="modal-actions">
|
||||
<div className="modal-actions-left">
|
||||
<small>
|
||||
Current directory: <code>{worktreesDirPickerCurrentPath === "." ? "(project root)" : worktreesDirPickerCurrentPath}</code>
|
||||
{t("settings.fileBrowser.currentDirectory", "Current directory:")} <code>{worktreesDirPickerCurrentPath === "." ? t("settings.fileBrowser.projectRoot", "(project root)") : worktreesDirPickerCurrentPath}</code>
|
||||
</small>
|
||||
</div>
|
||||
<div className="modal-actions-right">
|
||||
@@ -3156,7 +3160,7 @@ export function SettingsModal({
|
||||
<div className="modal modal-md">
|
||||
<div className="modal-header">
|
||||
<h3>{t("settings.importExport.importTitle", "Import Settings")}</h3>
|
||||
<button className="modal-close" onClick={() => setImportDialogOpen(false)} aria-label="Close">
|
||||
<button className="modal-close" onClick={() => setImportDialogOpen(false)} aria-label={t("actions.close", "Close")}>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
@@ -3165,7 +3169,7 @@ export function SettingsModal({
|
||||
|
||||
{importPreview.global && Object.keys(importPreview.global).length > 0 && (
|
||||
<div className="form-group">
|
||||
<strong>Global Settings:</strong>
|
||||
<strong>{t("settings.importExport.globalSettings", "Global Settings:")}</strong>
|
||||
<ul className="import-preview-list">
|
||||
{Object.entries(importPreview.global)
|
||||
.filter(([, v]) => v !== undefined)
|
||||
@@ -3178,7 +3182,7 @@ export function SettingsModal({
|
||||
|
||||
{importPreview.project && Object.keys(importPreview.project).length > 0 && (
|
||||
<div className="form-group">
|
||||
<strong>Project Settings:</strong>
|
||||
<strong>{t("settings.importExport.projectSettings", "Project Settings:")}</strong>
|
||||
<ul className="import-preview-list">
|
||||
{Object.entries(importPreview.project)
|
||||
.filter(([, v]) => v !== undefined)
|
||||
@@ -3190,15 +3194,15 @@ export function SettingsModal({
|
||||
)}
|
||||
|
||||
<div className="form-group">
|
||||
<label htmlFor="import-scope">Import Scope:</label>
|
||||
<label htmlFor="import-scope">{t("settings.importExport.importScope", "Import Scope:")}</label>
|
||||
<select
|
||||
id="import-scope"
|
||||
value={importScope}
|
||||
onChange={(e) => setImportScope(e.target.value as 'global' | 'project' | 'both')}
|
||||
>
|
||||
<option value="both">Both global and project settings</option>
|
||||
<option value="global">Global settings only</option>
|
||||
<option value="project">Project settings only</option>
|
||||
<option value="both">{t("settings.importExport.scopeBoth", "Both global and project settings")}</option>
|
||||
<option value="global">{t("settings.importExport.scopeGlobal", "Global settings only")}</option>
|
||||
<option value="project">{t("settings.importExport.scopeProject", "Project settings only")}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -3210,9 +3214,9 @@ export function SettingsModal({
|
||||
checked={importMerge}
|
||||
onChange={(e) => setImportMerge(e.target.checked)}
|
||||
/>
|
||||
Merge with existing settings (recommended)
|
||||
{t("settings.importExport.mergeExisting", "Merge with existing settings (recommended)")}
|
||||
</label>
|
||||
<small>If unchecked, existing settings will be replaced with imported values.</small>
|
||||
<small>{t("settings.importExport.replaceWarning", "If unchecked, existing settings will be replaced with imported values.")}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div className="modal-actions">
|
||||
|
||||
@@ -152,14 +152,14 @@ export function SetupWizardModal({
|
||||
{/* Header */}
|
||||
<div className="setup-wizard-header">
|
||||
<div className="setup-wizard-heading">
|
||||
<div className="setup-wizard-brand" aria-label="Fusion">
|
||||
<div className="setup-wizard-brand" aria-label={t("setup.brandName", "Fusion")}>
|
||||
<svg
|
||||
className="setup-wizard-brand-logo"
|
||||
width={28}
|
||||
height={28}
|
||||
viewBox="0 0 128 128"
|
||||
fill="none"
|
||||
aria-label="Fusion logo"
|
||||
aria-label={t("setup.brandLogo", "Fusion logo")}
|
||||
role="img"
|
||||
>
|
||||
<circle
|
||||
@@ -174,12 +174,12 @@ export function SetupWizardModal({
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
<span className="setup-wizard-brand-name">Fusion</span>
|
||||
<span className="setup-wizard-brand-name">{t("setup.brandName", "Fusion")}</span>
|
||||
</div>
|
||||
<h2 id="wizard-title" className="setup-wizard-title">
|
||||
{state.step === "auth" && t("setup.setAuthToken", "Set Auth Token")}
|
||||
{state.step === "manual" && t("setup.welcomeToFusion", "Welcome to Fusion")}
|
||||
{state.step === "complete" && t("setup.setupComplete", "Setup Complete!")}
|
||||
{state.step === "complete" && t("setup.setupCompleteTitle", "Setup Complete!")}
|
||||
</h2>
|
||||
</div>
|
||||
{state.step !== "complete" && (
|
||||
|
||||
@@ -3,6 +3,8 @@ import React, { useCallback, useLayoutEffect, useMemo, useRef, useState } from "
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import { ChevronDown, Loader2, Maximize2, Minimize2, Send } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { TFunction } from "i18next";
|
||||
import { addSteeringComment, refineTask } from "../api";
|
||||
import { useAgentLogs } from "../hooks/useAgentLogs";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
@@ -58,18 +60,18 @@ function isTranscriptNearBottom(container: HTMLElement): boolean {
|
||||
return container.scrollHeight - (container.scrollTop + container.clientHeight) <= BOTTOM_FOLLOW_THRESHOLD;
|
||||
}
|
||||
|
||||
function getRoleLabel(role: AgentLogRole): string {
|
||||
function getRoleLabel(role: AgentLogRole, t: TFunction<"app">): string {
|
||||
switch (role) {
|
||||
case "triage":
|
||||
return "Planner";
|
||||
return t("taskChat.roles.planner", "Planner");
|
||||
case "executor":
|
||||
return "Executor";
|
||||
return t("taskChat.roles.executor", "Executor");
|
||||
case "reviewer":
|
||||
return "Reviewer";
|
||||
return t("taskChat.roles.reviewer", "Reviewer");
|
||||
case "merger":
|
||||
return "Merger";
|
||||
return t("taskChat.roles.merger", "Merger");
|
||||
default:
|
||||
return "Agent";
|
||||
return t("taskChat.roles.agent", "Agent");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +139,7 @@ function mergeUserMessages(persistedComments: readonly SteeringComment[] | undef
|
||||
return messages;
|
||||
}
|
||||
|
||||
function buildTranscriptItems(entries: readonly AgentLogEntry[], userMessages: readonly UserChatMessage[]): TaskChatTranscriptItem[] {
|
||||
function buildTranscriptItems(entries: readonly AgentLogEntry[], userMessages: readonly UserChatMessage[], t: TFunction<"app">): TaskChatTranscriptItem[] {
|
||||
const orderedItems = [
|
||||
...entries.map((entry, index) => ({ kind: "agent" as const, entry, index, timestamp: getTimestampMs(entry.timestamp) })),
|
||||
...userMessages.map((message, index) => ({ kind: "user" as const, message, index, timestamp: getTimestampMs(message.createdAt) })),
|
||||
@@ -155,7 +157,7 @@ function buildTranscriptItems(entries: readonly AgentLogEntry[], userMessages: r
|
||||
previousItem.entries.push(item.entry);
|
||||
return items;
|
||||
}
|
||||
items.push({ kind: "agent", role, label: getRoleLabel(role), entries: [item.entry] });
|
||||
items.push({ kind: "agent", role, label: getRoleLabel(role, t), entries: [item.entry] });
|
||||
return items;
|
||||
}, []);
|
||||
}
|
||||
@@ -178,25 +180,29 @@ function isToolLikeEntry(entry: AgentLogEntry): boolean {
|
||||
return entry.type === "tool" || entry.type === "tool_result" || entry.type === "tool_error";
|
||||
}
|
||||
|
||||
function formatEntryLabel(entry: AgentLogEntry): string {
|
||||
function formatEntryLabel(entry: AgentLogEntry, t: TFunction<"app">): string {
|
||||
switch (entry.type) {
|
||||
case "tool":
|
||||
return "Tool call";
|
||||
return t("taskChat.toolCall", "Tool call");
|
||||
case "tool_result":
|
||||
return "Tool result";
|
||||
return t("taskChat.toolResult", "Tool result");
|
||||
case "tool_error":
|
||||
return "Tool error";
|
||||
return t("taskChat.toolError", "Tool error");
|
||||
case "thinking":
|
||||
return "Thinking";
|
||||
return t("taskChat.thinking", "Thinking");
|
||||
default:
|
||||
return "Message";
|
||||
return t("taskChat.message", "Message");
|
||||
}
|
||||
}
|
||||
|
||||
function formatCompletionLabel(entry: AgentLogEntry, t: TFunction<"app">): string {
|
||||
return entry.type === "tool_error" ? t("taskChat.error", "Error") : t("taskChat.result", "Result");
|
||||
}
|
||||
|
||||
const TOOL_NAME_SUMMARY_LIMIT = 5;
|
||||
|
||||
function formatToolCallCount(count: number): string {
|
||||
return count === 1 ? "1 tool call" : `${count} tool calls`;
|
||||
function formatToolCallCount(count: number, t: TFunction<"app">): string {
|
||||
return t("taskChat.toolCallCount", "{{count}} tool call", { count });
|
||||
}
|
||||
|
||||
function getToolInvocationEntries(entries: AgentLogEntry[]): AgentLogEntry[] {
|
||||
@@ -270,12 +276,14 @@ function TaskChatText({ entries }: { entries: AgentLogEntry[] }) {
|
||||
}
|
||||
|
||||
function TaskChatToolEntry({ entry }: { entry: AgentLogEntry }) {
|
||||
const { t } = useTranslation("app");
|
||||
|
||||
return (
|
||||
<article
|
||||
className={`task-chat-tool-entry task-chat-tool-entry--${entry.type.replace("_", "-")}`}
|
||||
data-testid={`task-chat-entry-${entry.type}`}
|
||||
>
|
||||
<div className="task-chat-entry-kicker">{formatEntryLabel(entry)}</div>
|
||||
<div className="task-chat-entry-kicker">{formatEntryLabel(entry, t)}</div>
|
||||
<div className="task-chat-entry-text">{entry.text}</div>
|
||||
{entry.detail ? <pre className="task-chat-tool-detail">{linkifyFilePaths(entry.detail)}</pre> : null}
|
||||
</article>
|
||||
@@ -310,25 +318,26 @@ function getToolGroupRows(entries: AgentLogEntry[]): TaskChatToolGroupRow[] {
|
||||
}
|
||||
|
||||
function TaskChatToolInvocation({ row }: { row: Extract<TaskChatToolGroupRow, { kind: "invocation" }> }) {
|
||||
const { t } = useTranslation("app");
|
||||
const completion = row.completion;
|
||||
const completionLabel = completion ? formatEntryLabel(completion).replace("Tool ", "") : undefined;
|
||||
const completionLabel = completion ? formatCompletionLabel(completion, t) : undefined;
|
||||
const className = `task-chat-tool-entry task-chat-tool-invocation${completion?.type === "tool_error" ? " task-chat-tool-entry--tool-error" : ""}`;
|
||||
|
||||
return (
|
||||
<article className={className} data-testid="task-chat-tool-invocation">
|
||||
<div className="task-chat-entry-kicker">
|
||||
{completionLabel ? `Tool call → ${completionLabel}` : "Tool call"}
|
||||
{completionLabel ? t("taskChat.toolCallTo", "Tool call → {{label}}", { label: completionLabel }) : t("taskChat.toolCall", "Tool call")}
|
||||
</div>
|
||||
<div className="task-chat-entry-text">{row.call.text}</div>
|
||||
{row.call.detail ? (
|
||||
<div className="task-chat-tool-detail-block">
|
||||
<div className="task-chat-tool-detail-label">Arguments</div>
|
||||
<div className="task-chat-tool-detail-label">{t("taskChat.arguments", "Arguments")}</div>
|
||||
<pre className="task-chat-tool-detail">{linkifyFilePaths(row.call.detail)}</pre>
|
||||
</div>
|
||||
) : null}
|
||||
{completion?.detail ? (
|
||||
<div className="task-chat-tool-detail-block">
|
||||
<div className="task-chat-tool-detail-label">{completion.type === "tool_error" ? "Error" : "Result"}</div>
|
||||
<div className="task-chat-tool-detail-label">{completion.type === "tool_error" ? t("taskChat.error", "Error") : t("taskChat.result", "Result")}</div>
|
||||
<pre className="task-chat-tool-detail">{linkifyFilePaths(completion.detail)}</pre>
|
||||
</div>
|
||||
) : null}
|
||||
@@ -337,6 +346,7 @@ function TaskChatToolInvocation({ row }: { row: Extract<TaskChatToolGroupRow, {
|
||||
}
|
||||
|
||||
function TaskChatToolGroup({ entries }: { entries: AgentLogEntry[] }) {
|
||||
const { t } = useTranslation("app");
|
||||
const invocationEntries = getToolInvocationEntries(entries);
|
||||
const invocationCount = invocationEntries.length;
|
||||
const errorCount = entries.filter((entry) => entry.type === "tool_error").length;
|
||||
@@ -346,16 +356,16 @@ function TaskChatToolGroup({ entries }: { entries: AgentLogEntry[] }) {
|
||||
return (
|
||||
<details className="task-chat-tool-group" data-testid="task-chat-tool-group">
|
||||
<summary className="task-chat-tool-group-summary">
|
||||
<span className="task-chat-tool-group-count">{formatToolCallCount(invocationCount)}</span>
|
||||
<span className="task-chat-tool-group-count">{formatToolCallCount(invocationCount, t)}</span>
|
||||
{visibleNames.length > 0 ? (
|
||||
<span className="task-chat-tool-group-names" aria-label="Tool names">
|
||||
<span className="task-chat-tool-group-names" aria-label={t("taskChat.toolNames", "Tool names")}>
|
||||
{visibleNames.join(", ")}
|
||||
{overflowCount > 0 ? <span className="task-chat-tool-group-overflow">, +{overflowCount} more</span> : null}
|
||||
{overflowCount > 0 ? <span className="task-chat-tool-group-overflow">{t("taskChat.moreTools", ", +{{count}} more", { count: overflowCount })}</span> : null}
|
||||
</span>
|
||||
) : null}
|
||||
{errorCount > 0 ? (
|
||||
<span className="task-chat-tool-group-error-count">
|
||||
{errorCount === 1 ? "1 error" : `${errorCount} errors`}
|
||||
{t("taskChat.errorCount", "{{count}} error", { count: errorCount })}
|
||||
</span>
|
||||
) : null}
|
||||
</summary>
|
||||
@@ -373,11 +383,12 @@ function TaskChatToolGroup({ entries }: { entries: AgentLogEntry[] }) {
|
||||
}
|
||||
|
||||
function TaskChatThinking({ entries }: { entries: AgentLogEntry[] }) {
|
||||
const { t } = useTranslation("app");
|
||||
const combinedThinkingText = entries.map((entry) => entry.text).join("");
|
||||
|
||||
return (
|
||||
<details className="task-chat-thinking" data-testid="task-chat-thinking" open>
|
||||
<summary className="task-chat-thinking-summary">Thinking</summary>
|
||||
<summary className="task-chat-thinking-summary">{t("taskChat.thinking", "Thinking")}</summary>
|
||||
<div className="task-chat-thinking-body">
|
||||
<div
|
||||
className="markdown-body task-chat-markdown task-chat-thinking-markdown"
|
||||
@@ -407,12 +418,13 @@ FNXC:TaskChatTimestamps 2026-06-17-15:43:
|
||||
FN-6597 requires small relative timestamps on both task-chat agent group headers and user message headers, computed at render time from existing transcript timestamps without adding a live timer.
|
||||
*/
|
||||
function TaskChatUserMessage({ message }: { message: UserChatMessage }) {
|
||||
const { t } = useTranslation("app");
|
||||
const relativeTime = formatRelativeTimeAgo(message.createdAt);
|
||||
|
||||
return (
|
||||
<section className="task-chat-user-group" aria-label="You message">
|
||||
<section className="task-chat-user-group" aria-label={t("taskChat.youMessage", "You message")}>
|
||||
<div className="task-chat-user-header">
|
||||
<div className="task-chat-role-label">You</div>
|
||||
<div className="task-chat-role-label">{t("taskChat.you", "You")}</div>
|
||||
{relativeTime ? (
|
||||
<span className="task-chat-timestamp" data-testid="task-chat-user-time">
|
||||
{relativeTime}
|
||||
@@ -431,6 +443,7 @@ function TaskChatUserMessage({ message }: { message: UserChatMessage }) {
|
||||
}
|
||||
|
||||
export function TaskChatTab({ task, projectId, active, addToast, sessionLive, onTaskUpdated, expanded = false, onToggleExpanded }: TaskChatTabProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const { entries, loading, loadMore, hasMore, loadingMore } = useAgentLogs(task.id, active, projectId);
|
||||
const [draft, setDraft] = useState("");
|
||||
const [sending, setSending] = useState(false);
|
||||
@@ -452,7 +465,7 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
||||
() => mergeUserMessages(task.steeringComments, optimisticMessages),
|
||||
[optimisticMessages, task.steeringComments],
|
||||
);
|
||||
const transcriptItems = useMemo(() => buildTranscriptItems(entries, userMessages), [entries, userMessages]);
|
||||
const transcriptItems = useMemo(() => buildTranscriptItems(entries, userMessages, t), [entries, t, userMessages]);
|
||||
const transcriptItemCount = entries.length + userMessages.length;
|
||||
const firstEntryKey = entries[0] ? getEntryKey(entries[0], 0) : null;
|
||||
const activeSession = isActiveAgentSession(task, { sessionLive });
|
||||
@@ -463,13 +476,13 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
||||
* The task-detail chat must never silently accept a question when no agent session will consume it. Keep idle chats sendable, but surface that the message is saved as guidance for the next task run instead of implying a live reply.
|
||||
*/
|
||||
const sessionHint = isDoneTask
|
||||
? "Send a message to start a refinement task for this completed task."
|
||||
? t("taskChat.doneSessionHint", "Send a message to start a refinement task for this completed task.")
|
||||
: activeSession
|
||||
? "Message the active agent session. Guidance is delivered to the running session in real time."
|
||||
: "No agent is working on this task right now. Your message is saved as guidance and will reach an agent the next time this task runs.";
|
||||
? t("taskChat.activeSessionHint", "Message the active agent session. Guidance is delivered to the running session in real time.")
|
||||
: t("taskChat.idleSessionHint", "No agent is working on this task right now. Your message is saved as guidance and will reach an agent the next time this task runs.");
|
||||
const composerPlaceholder = isDoneTask
|
||||
? "Start a refinement task for this completed task"
|
||||
: "Steer the currently executing agent";
|
||||
? t("taskChat.donePlaceholder", "Start a refinement task for this completed task")
|
||||
: t("taskChat.activePlaceholder", "Steer the currently executing agent");
|
||||
const canSend = draft.trim().length > 0 && !sending;
|
||||
|
||||
const resizeComposer = useCallback(() => {
|
||||
@@ -711,7 +724,7 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm task-chat-expand-toggle task-chat-expand-toggle--overlay"
|
||||
onClick={onToggleExpanded}
|
||||
aria-label={expanded ? "Collapse chat" : "Expand chat to full modal"}
|
||||
aria-label={expanded ? t("taskChat.collapseChat", "Collapse chat") : t("taskChat.expandChat", "Expand chat to full modal")}
|
||||
aria-pressed={expanded}
|
||||
data-testid="task-chat-expand-toggle"
|
||||
>
|
||||
@@ -731,17 +744,17 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
||||
{loadingMore ? (
|
||||
<div className="task-chat-load-previous-status" role="status" data-testid="task-chat-load-previous-loading">
|
||||
<Loader2 className="animate-spin" aria-hidden="true" />
|
||||
<span>Loading earlier messages…</span>
|
||||
<span>{t("taskChat.loadingEarlierMessages", "Loading earlier messages…")}</span>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-secondary btn-sm task-chat-load-previous"
|
||||
onClick={() => { void loadPreviousMessages(); }}
|
||||
aria-label="Load previous messages"
|
||||
aria-label={t("taskChat.loadPreviousMessages", "Load previous messages")}
|
||||
data-testid="task-chat-load-previous"
|
||||
>
|
||||
Load previous messages
|
||||
{t("taskChat.loadPreviousMessages", "Load previous messages")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -749,10 +762,10 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
||||
{loading && transcriptItemCount === 0 ? (
|
||||
<div className="task-chat-empty" role="status">
|
||||
<Loader2 className="animate-spin" aria-hidden="true" />
|
||||
<span>Loading agent output…</span>
|
||||
<span>{t("taskChat.loadingAgentOutput", "Loading agent output…")}</span>
|
||||
</div>
|
||||
) : transcriptItemCount === 0 ? (
|
||||
<div className="task-chat-empty">No agent output yet. Live messages from Planner, Executor, Reviewer, and Merger agents will appear here.</div>
|
||||
<div className="task-chat-empty">{t("taskChat.emptyAgentOutput", "No agent output yet. Live messages from Planner, Executor, Reviewer, and Merger agents will appear here.")}</div>
|
||||
) : (
|
||||
transcriptItems.map((item, itemIndex) => {
|
||||
if (item.kind === "user") {
|
||||
@@ -768,13 +781,13 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
||||
const latestEntryTimestamp = item.entries[item.entries.length - 1]?.timestamp ?? "";
|
||||
const relativeTime = formatRelativeTimeAgo(latestEntryTimestamp);
|
||||
return (
|
||||
<section className="task-chat-group" key={`${item.role ?? "agent"}-${itemIndex}`} aria-label={`${item.label} messages`}>
|
||||
<section className="task-chat-group" key={`${item.role ?? "agent"}-${itemIndex}`} aria-label={t("taskChat.agentMessages", "{{label}} messages", { label: item.label })}>
|
||||
<header className="task-chat-group-header">
|
||||
<AgentAvatar agent={avatarAgent} className="task-chat-avatar" />
|
||||
<div>
|
||||
<div className="task-chat-role-label">{item.label}</div>
|
||||
<div className="task-chat-group-meta">
|
||||
<span>{item.entries.length === 1 ? "1 entry" : `${item.entries.length} entries`}</span>
|
||||
<span>{t("taskChat.entryCount", "{{count}} entry", { count: item.entries.length })}</span>
|
||||
{relativeTime ? (
|
||||
<span className="task-chat-timestamp" data-testid="task-chat-group-time">
|
||||
{relativeTime}
|
||||
@@ -798,11 +811,11 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
||||
type="button"
|
||||
className="task-chat-jump-to-bottom"
|
||||
onClick={scrollTranscriptToBottom}
|
||||
aria-label="Jump to latest message"
|
||||
aria-label={t("taskChat.jumpToLatestMessage", "Jump to latest message")}
|
||||
data-testid="task-chat-jump-to-bottom"
|
||||
>
|
||||
<ChevronDown aria-hidden="true" />
|
||||
<span>Latest</span>
|
||||
<span>{t("taskChat.latest", "Latest")}</span>
|
||||
</button>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -826,15 +839,15 @@ export function TaskChatTab({ task, projectId, active, addToast, sessionLive, on
|
||||
onChange={(event) => setDraft(event.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
disabled={sending}
|
||||
aria-label="Message active agent session"
|
||||
aria-label={t("taskChat.messageActiveAgentSession", "Message active agent session")}
|
||||
rows={1}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary btn-icon task-chat-send"
|
||||
disabled={!canSend}
|
||||
aria-label={sending ? "Sending" : "Send"}
|
||||
title={sending ? "Sending" : "Send"}
|
||||
aria-label={sending ? t("taskChat.sending", "Sending") : t("common:actions.send", "Send")}
|
||||
title={sending ? t("taskChat.sending", "Sending") : t("common:actions.send", "Send")}
|
||||
>
|
||||
{sending ? <Loader2 className="animate-spin" aria-hidden="true" /> : <Send aria-hidden="true" />}
|
||||
</button>
|
||||
|
||||
@@ -115,7 +115,7 @@ export function TaskComments({ task, onTaskUpdated, addToast, currentAuthor = "u
|
||||
<div className="detail-log-header comments-header-row">
|
||||
<div className="comments-author-row">
|
||||
{isAIGuidance ? (
|
||||
<span className="ai-guidance-badge" data-testid="ai-guidance-badge">AI Guidance</span>
|
||||
<span className="ai-guidance-badge" data-testid="ai-guidance-badge">{t("comments.aiGuidance", "AI Guidance")}</span>
|
||||
) : (
|
||||
<strong>{comment.author}</strong>
|
||||
)}
|
||||
|
||||
@@ -71,14 +71,20 @@ interface ModelSelection {
|
||||
|
||||
const ACTIVE_STATUSES = new Set(["planning", "researching", "executing", "finalizing", "merging", "merging-fix"]);
|
||||
const STALE_PAUSED_REVIEW_LOG_REGEX = /^Stale paused review surfaced \[([^\]]+)\]/;
|
||||
const EMPTY_MARKDOWN_CHILD_SEPARATOR = "";
|
||||
const STRING_OBJECT_TAG = "[object String]";
|
||||
|
||||
function isStringValue(value: unknown): value is string {
|
||||
return Object.prototype.toString.call(value) === STRING_OBJECT_TAG;
|
||||
}
|
||||
|
||||
const markdownLinkifyComponents: Components = {
|
||||
p: ({ children, ...props }) => <p {...props}>{linkifyReactChildren(children)}</p>,
|
||||
li: ({ children, ...props }) => <li {...props}>{linkifyReactChildren(children)}</li>,
|
||||
code: ({ children, ...props }) => {
|
||||
const text = typeof children === "string" ? children : React.Children.toArray(children).join("");
|
||||
const text = React.Children.toArray(children).join(EMPTY_MARKDOWN_CHILD_SEPARATOR);
|
||||
const linkedChildren = linkifyFilePaths(text);
|
||||
if (linkedChildren.length === 1 && typeof linkedChildren[0] === "string") {
|
||||
if (linkedChildren.length === 1 && linkedChildren[0]?.constructor === String) {
|
||||
return <code {...props}>{children}</code>;
|
||||
}
|
||||
return <code {...props}>{linkedChildren}</code>;
|
||||
@@ -92,25 +98,25 @@ const markdownLinkifyComponents: Components = {
|
||||
*/
|
||||
function extractExecutorModelFromLog(entries: AgentLogEntry[]): { provider: string; modelId: string } | null {
|
||||
let result: { provider: string; modelId: string } | null = null;
|
||||
for (const entry of entries) {
|
||||
if (entry.agent !== "executor" || entry.type !== "text") continue;
|
||||
entries.forEach((entry) => {
|
||||
if (entry.agent !== "executor" || entry.type !== "text") return;
|
||||
const match = entry.text.match(/^Executor using model: (.+?)\/(.+)$/);
|
||||
if (match) {
|
||||
result = { provider: match[1], modelId: match[2] };
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function extractReviewerModelFromLog(entries: AgentLogEntry[]): { provider: string; modelId: string } | null {
|
||||
let result: { provider: string; modelId: string } | null = null;
|
||||
for (const entry of entries) {
|
||||
if (entry.agent !== "reviewer" || entry.type !== "text") continue;
|
||||
entries.forEach((entry) => {
|
||||
if (entry.agent !== "reviewer" || entry.type !== "text") return;
|
||||
const match = entry.text.match(/^Reviewer using model: (.+?)\/(.+)$/);
|
||||
if (match) {
|
||||
result = { provider: match[1], modelId: match[2] };
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -129,7 +135,7 @@ function hasUsableTrackingTitle(task: { title?: string | null; description?: str
|
||||
|
||||
function extractAssignedRuntimeModel(agent: Agent | null | undefined): ModelSelection {
|
||||
const runtimeConfig = (agent?.runtimeConfig ?? undefined) as Record<string, unknown> | undefined;
|
||||
const model = typeof runtimeConfig?.model === "string" ? runtimeConfig.model.trim() : "";
|
||||
const model = isStringValue(runtimeConfig?.model) ? runtimeConfig.model.trim() : "";
|
||||
if (model) {
|
||||
const slashIdx = model.indexOf("/");
|
||||
if (slashIdx > 0 && slashIdx < model.length - 1) {
|
||||
@@ -140,8 +146,8 @@ function extractAssignedRuntimeModel(agent: Agent | null | undefined): ModelSele
|
||||
}
|
||||
}
|
||||
|
||||
const provider = typeof runtimeConfig?.modelProvider === "string" ? runtimeConfig.modelProvider.trim() : "";
|
||||
const modelId = typeof runtimeConfig?.modelId === "string" ? runtimeConfig.modelId.trim() : "";
|
||||
const provider = isStringValue(runtimeConfig?.modelProvider) ? runtimeConfig.modelProvider.trim() : "";
|
||||
const modelId = isStringValue(runtimeConfig?.modelId) ? runtimeConfig.modelId.trim() : "";
|
||||
return {
|
||||
provider: provider || undefined,
|
||||
modelId: modelId || undefined,
|
||||
@@ -209,13 +215,13 @@ function resolveEffectiveValidator(
|
||||
function extractPlanningModelFromLog(entries: AgentLogEntry[]): { provider: string; modelId: string } | null {
|
||||
// Iterate in chronological order; last match wins
|
||||
let result: { provider: string; modelId: string } | null = null;
|
||||
for (const entry of entries) {
|
||||
if (entry.agent !== "triage" || entry.type !== "text") continue;
|
||||
entries.forEach((entry) => {
|
||||
if (entry.agent !== "triage" || entry.type !== "text") return;
|
||||
const match = entry.text.match(/^Triage using model: (.+?)\/(.+)$/);
|
||||
if (match) {
|
||||
result = { provider: match[1], modelId: match[2] };
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -431,7 +437,7 @@ function normalizeSourceIssueUrl(value: string): string | undefined {
|
||||
}
|
||||
|
||||
function normalizeTaskPriorityValue(priority: Task["priority"]): TaskPriority {
|
||||
return typeof priority === "string" && (TASK_PRIORITIES as readonly string[]).includes(priority)
|
||||
return isStringValue(priority) && (TASK_PRIORITIES as readonly string[]).includes(priority)
|
||||
? (priority as TaskPriority)
|
||||
: DEFAULT_TASK_PRIORITY;
|
||||
}
|
||||
@@ -456,7 +462,7 @@ interface ProvenanceLabelOptions {
|
||||
|
||||
function getIssueUrlFromMetadata(metadata: Task["sourceMetadata"]): string | undefined {
|
||||
const issueUrl = metadata?.issueUrl;
|
||||
return typeof issueUrl === "string" && issueUrl.length > 0 ? issueUrl : undefined;
|
||||
return isStringValue(issueUrl) && issueUrl.length > 0 ? issueUrl : undefined;
|
||||
}
|
||||
|
||||
function parseGithubIssueLabel(url: string): { label: string; href: string } | null {
|
||||
@@ -474,12 +480,12 @@ function parseGithubIssueLabel(url: string): { label: string; href: string } | n
|
||||
|
||||
function getResearchContextInfo(metadata: Task["sourceMetadata"]): string | undefined {
|
||||
const findingLabel = metadata?.findingLabel;
|
||||
if (typeof findingLabel === "string" && findingLabel.length > 0) {
|
||||
if (isStringValue(findingLabel) && findingLabel.length > 0) {
|
||||
return findingLabel;
|
||||
}
|
||||
|
||||
const runId = metadata?.runId;
|
||||
return typeof runId === "string" && runId.length > 0 ? runId : undefined;
|
||||
return isStringValue(runId) && runId.length > 0 ? runId : undefined;
|
||||
}
|
||||
|
||||
const AgentDetailView = lazy(() => import("./AgentDetailView").then((m) => ({ default: m.AgentDetailView })));
|
||||
@@ -659,7 +665,7 @@ export function TaskDetailContent({
|
||||
(task.stuckKillCount ?? 0) > 0 ||
|
||||
(task.recoveryRetryCount ?? 0) > 0 ||
|
||||
Boolean(task.nextRecoveryAt);
|
||||
const nearDuplicateOf = typeof workingTask.sourceMetadata?.nearDuplicateOf === "string"
|
||||
const nearDuplicateOf = isStringValue(workingTask.sourceMetadata?.nearDuplicateOf)
|
||||
? workingTask.sourceMetadata.nearDuplicateOf
|
||||
: null;
|
||||
const nearDuplicateCanonical = nearDuplicateOf
|
||||
@@ -810,11 +816,11 @@ export function TaskDetailContent({
|
||||
setCustomFieldValues(updated.customFields ?? {});
|
||||
onTaskUpdated?.(updated);
|
||||
} catch (err) {
|
||||
if (err instanceof ApiRequestError && err.details && typeof err.details.fieldId === "string") {
|
||||
if (err instanceof ApiRequestError && err.details && isStringValue(err.details.fieldId)) {
|
||||
setCustomFieldError({
|
||||
code: (err.details.code as CustomFieldRejection["code"]) ?? "type-mismatch",
|
||||
fieldId: err.details.fieldId,
|
||||
detail: typeof err.details.detail === "string" ? err.details.detail : err.message,
|
||||
detail: isStringValue(err.details.detail) ? err.details.detail : err.message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -909,7 +915,7 @@ export function TaskDetailContent({
|
||||
tabId: `plugin-${entry.pluginId}-${index}` as TabId,
|
||||
}));
|
||||
const activePluginTab =
|
||||
typeof activeTab === "string" && activeTab.startsWith("plugin-")
|
||||
isStringValue(activeTab) && activeTab.startsWith("plugin-")
|
||||
? pluginTabs.find((tab) => tab.tabId === activeTab) ?? null
|
||||
: null;
|
||||
|
||||
@@ -2981,7 +2987,7 @@ export function TaskDetailContent({
|
||||
)}
|
||||
{provenanceDisplay.parentTaskId && (
|
||||
<>
|
||||
{" "}of{" "}
|
||||
{" "}{t("taskDetail.provenance.parentTaskOf", "of")}{" "}
|
||||
<button
|
||||
type="button"
|
||||
className="detail-provenance-link"
|
||||
@@ -3021,7 +3027,7 @@ export function TaskDetailContent({
|
||||
<div className="detail-provenance detail-pr-link-row">
|
||||
<GitBranch aria-hidden="true" />
|
||||
<span>
|
||||
PR{" "}
|
||||
{t("taskDetail.pr.label", "PR")} {" "}
|
||||
{task.prInfo?.url ? (
|
||||
<a
|
||||
className="detail-provenance-link"
|
||||
|
||||
@@ -42,6 +42,11 @@ import type { DiscoveredSkill } from "../api";
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { useOverlayDismiss } from "../hooks/useOverlayDismiss";
|
||||
import { useConfirm } from "../hooks/useConfirm";
|
||||
|
||||
/*
|
||||
FNXC:i18n-Localize 2026-06-20-00:00:
|
||||
FN-6770 localizes this workflow surface through t() and authored en catalog keys so hardcoded user-facing copy does not need a lint.ignore deferral.
|
||||
*/
|
||||
import { useModalResizePersist } from "../hooks/useModalResizePersist";
|
||||
import { useAppSettings } from "../hooks/useAppSettings";
|
||||
import { isMobileViewport, useViewportMode } from "../hooks/useViewportMode";
|
||||
@@ -210,6 +215,9 @@ const NOTIFY_EVENT_OPTIONS = [
|
||||
"workflow-notify",
|
||||
] as const;
|
||||
const NOTIFY_CUSTOM_EVENT_VALUE = "__custom";
|
||||
const WORKFLOW_CLI_COMMAND_PLACEHOLDER = "npm test -- --runInBand";
|
||||
const WORKFLOW_CODE_SOURCE_PLACEHOLDER = "export default async (ctx) => ({ outcome: \"success\" });";
|
||||
const WORKFLOW_NOTIFY_MESSAGE_PLACEHOLDER = "Task {{taskId}} reached {{workflowName}}";
|
||||
|
||||
const PALETTE: Array<{ kind: WorkflowEditorNodeKind; label: string; icon: typeof MessageSquare; presetConfig?: Record<string, unknown> }> = [
|
||||
{ kind: "prompt", label: "Prompt", icon: MessageSquare },
|
||||
@@ -1019,11 +1027,11 @@ function InnerEditor({
|
||||
return isMobileMode ? null : data[0]?.id ?? null;
|
||||
});
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err) || "Failed to load workflows", "error");
|
||||
addToast(getErrorMessage(err) || t("workflows.loadFailed", "Failed to load workflows"), "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [projectId, addToast, isMobileMode, initialAction, initialWorkflowId]);
|
||||
}, [projectId, addToast, isMobileMode, initialAction, initialWorkflowId, t]);
|
||||
|
||||
useEffect(() => {
|
||||
void loadWorkflows();
|
||||
@@ -1752,11 +1760,11 @@ function InnerEditor({
|
||||
setWorkflows((ws) => [...ws, created]);
|
||||
setActiveId(created.id);
|
||||
setWorkflowListStageOpen(false);
|
||||
addToast(`Duplicated to "${created.name}" — editable`, "success");
|
||||
addToast(t("workflows.duplicatedEditable", "Duplicated to \"{{name}}\" — editable", { name: created.name }), "success");
|
||||
} catch (err) {
|
||||
addToast(getErrorMessage(err) || "Failed to duplicate workflow", "error");
|
||||
addToast(getErrorMessage(err) || t("workflows.duplicateFailed", "Failed to duplicate workflow"), "error");
|
||||
}
|
||||
}, [activeWorkflow, projectId, addToast]);
|
||||
}, [activeWorkflow, projectId, addToast, t]);
|
||||
|
||||
const handleSave = useCallback(async () => {
|
||||
if (!activeWorkflow) return;
|
||||
@@ -2107,25 +2115,25 @@ function InnerEditor({
|
||||
// step-review offers an optional review model picker (KTD-4).
|
||||
if (selectedNode?.data.kind === "step-review" && models.length === 0) {
|
||||
fetchModels().then((res) => setModels(res.models)).catch((err) => {
|
||||
addToast(getErrorMessage(err) || "Failed to load models", "error");
|
||||
addToast(getErrorMessage(err) || t("workflowEditor.modelsLoadFailed", "Failed to load models"), "error");
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!selectedNode || (selectedNode.data.kind !== "prompt" && selectedNode.data.kind !== "gate")) return;
|
||||
if (currentExecutor === "model" && models.length === 0) {
|
||||
fetchModels().then((res) => setModels(res.models)).catch((err) => {
|
||||
addToast(getErrorMessage(err) || "Failed to load models", "error");
|
||||
addToast(getErrorMessage(err) || t("workflowEditor.modelsLoadFailed", "Failed to load models"), "error");
|
||||
});
|
||||
} else if (currentExecutor === "agent" && agents.length === 0) {
|
||||
// Project-scoped, matching WorkflowColumnPanel's fetchAgents(undefined,
|
||||
// projectId) — an unscoped fetch returns the wrong registry in
|
||||
// multi-project deployments (PR #1432 review).
|
||||
fetchAgents(undefined, projectId).then(setAgents).catch((err) => {
|
||||
addToast(getErrorMessage(err) || "Failed to load agents", "error");
|
||||
addToast(getErrorMessage(err) || t("workflowEditor.agentsLoadFailed", "Failed to load agents"), "error");
|
||||
});
|
||||
} else if (currentExecutor === "skill" && skills.length === 0) {
|
||||
fetchDiscoveredSkills(projectId).then(setSkills).catch((err) => {
|
||||
addToast(getErrorMessage(err) || "Failed to load skills", "error");
|
||||
addToast(getErrorMessage(err) || t("workflowEditor.skillsLoadFailed", "Failed to load skills"), "error");
|
||||
});
|
||||
}
|
||||
}, [
|
||||
@@ -2137,6 +2145,7 @@ function InnerEditor({
|
||||
models.length,
|
||||
agents.length,
|
||||
skills.length,
|
||||
t,
|
||||
]);
|
||||
|
||||
// ── Dirty-state dismissal guard (U4, R7) ────────────────────────────────────
|
||||
@@ -2198,12 +2207,12 @@ function InnerEditor({
|
||||
Promise.resolve(fetchAgents(undefined, projectId)).then((list) => {
|
||||
if (!cancelled) setAgents(list ?? []);
|
||||
}).catch((err) => {
|
||||
if (!cancelled) addToast(getErrorMessage(err) || "Failed to load agents", "error");
|
||||
if (!cancelled) addToast(getErrorMessage(err) || t("workflowEditor.agentsLoadFailed", "Failed to load agents"), "error");
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [overrideColumnBinding, agents.length, projectId, addToast]);
|
||||
}, [overrideColumnBinding, agents.length, projectId, addToast, t]);
|
||||
|
||||
const overlayProps = useOverlayDismiss(requestClose);
|
||||
const promptFullscreenOverlay =
|
||||
@@ -2226,7 +2235,7 @@ function InnerEditor({
|
||||
</button>
|
||||
</div>
|
||||
<label className="wf-field">
|
||||
<span>Prompt</span>
|
||||
<span>{t("workflowEditor.prompt", "Prompt")}</span>
|
||||
<textarea
|
||||
rows={undefined}
|
||||
value={selectedNodePromptValue}
|
||||
@@ -2263,8 +2272,8 @@ function InnerEditor({
|
||||
}}
|
||||
>
|
||||
<header className="wf-editor-header">
|
||||
<h2>Workflows</h2>
|
||||
<button className="wf-editor-close" onClick={requestClose} aria-label="Close workflow editor">
|
||||
<h2>{t("workflows.title", "Workflows")}</h2>
|
||||
<button className="wf-editor-close" onClick={requestClose} aria-label={t("workflows.closeEditor", "Close workflow editor")}>
|
||||
<X size={18} />
|
||||
</button>
|
||||
</header>
|
||||
@@ -2349,10 +2358,10 @@ function InnerEditor({
|
||||
) : null}
|
||||
{loading ? (
|
||||
<div className="wf-editor-empty">
|
||||
<Loader2 size={16} className="wf-spin" /> Loading…
|
||||
<Loader2 size={16} className="wf-spin" /> {t("workflows.loading", "Loading…")}
|
||||
</div>
|
||||
) : workflows.length === 0 ? (
|
||||
<div className="wf-editor-empty">No workflows yet.</div>
|
||||
<div className="wf-editor-empty">{t("workflows.noneYet", "No workflows yet.")}</div>
|
||||
) : (
|
||||
<ul className="wf-editor-list">
|
||||
{workflows.map((w) => (
|
||||
@@ -3236,7 +3245,7 @@ function InnerEditor({
|
||||
!(compactLayoutEnabled && !isMobileMode) && (
|
||||
<aside className="wf-editor-inspector" data-testid="wf-node-inspector">
|
||||
<div className="wf-inspector-heading">
|
||||
<h3>Node</h3>
|
||||
<h3>{t("workflowNodes.nodeInspector", "Node")}</h3>
|
||||
{isMobileMode && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -3258,14 +3267,14 @@ function InnerEditor({
|
||||
</div>
|
||||
{isBuiltin && (
|
||||
<p className="wf-inspector-note wf-inspector-note--info">
|
||||
Read-only built-in — duplicate the workflow to edit nodes.
|
||||
{t("workflowNodes.readOnlyDuplicateToEdit", "Read-only built-in — duplicate the workflow to edit nodes.")}
|
||||
</p>
|
||||
)}
|
||||
<fieldset className="wf-inspector-fields" disabled={isBuiltin}>
|
||||
{/* FNXC:WorkflowEditor 2026-06-17-00:20: Start labels are structural and ignored by flowToIr, so exposing the generic Name editor would create a no-op rename. */}
|
||||
{selectedNode.data.kind !== "start" && (
|
||||
<label className="wf-field">
|
||||
<span>Name</span>
|
||||
<span>{t("common:labels.name", "Name")}</span>
|
||||
<input
|
||||
value={selectedNode.data.label}
|
||||
onChange={(e) => updateSelectedData({ label: e.target.value })}
|
||||
@@ -3307,7 +3316,7 @@ function InnerEditor({
|
||||
{selectedNode.data.kind === "prompt" || selectedNode.data.kind === "gate" ? (
|
||||
<div className="wf-prompt-editor">
|
||||
<label className="wf-field">
|
||||
<span>Prompt</span>
|
||||
<span>{t("workflowEditor.prompt", "Prompt")}</span>
|
||||
<textarea
|
||||
rows={5}
|
||||
value={selectedNodePromptValue}
|
||||
@@ -3334,15 +3343,15 @@ function InnerEditor({
|
||||
{selectedNode.data.kind === "prompt" ? (
|
||||
<>
|
||||
<label className="wf-field">
|
||||
<span>Executor</span>
|
||||
<span>{t("workflowEditor.executor", "Executor")}</span>
|
||||
<select
|
||||
value={currentExecutor}
|
||||
onChange={(e) => updateSelectedData({ config: { executor: e.target.value } })}
|
||||
>
|
||||
<option value="model">Model</option>
|
||||
<option value="agent">Agent</option>
|
||||
<option value="skill">Skill</option>
|
||||
<option value="cli">CLI / script</option>
|
||||
<option value="model">{t("workflowEditor.model", "Model")}</option>
|
||||
<option value="agent">{t("workflowEditor.agent", "Agent")}</option>
|
||||
<option value="skill">{t("workflowEditor.skill", "Skill")}</option>
|
||||
<option value="cli">{t("workflowEditor.cliScript", "CLI / script")}</option>
|
||||
<option value="cli-agent">{t("workflowEditor.cliAgent.executorOption")}</option>
|
||||
</select>
|
||||
</label>
|
||||
@@ -3362,9 +3371,9 @@ function InnerEditor({
|
||||
|
||||
{currentExecutor === "model" && (
|
||||
<label className="wf-field">
|
||||
<span>Model</span>
|
||||
<span>{t("workflowEditor.model", "Model")}</span>
|
||||
<CustomModelDropdown
|
||||
label="Model"
|
||||
label={t("workflowEditor.model", "Model")}
|
||||
models={models}
|
||||
value={getModelDropdownValue(
|
||||
String(selectedNode.data.config?.modelProvider ?? ""),
|
||||
@@ -3386,12 +3395,12 @@ function InnerEditor({
|
||||
const nodeAgentStale = nodeAgentId !== "" && !agents.some((a) => a.id === nodeAgentId);
|
||||
return (
|
||||
<label className="wf-field">
|
||||
<span>Agent</span>
|
||||
<span>{t("workflowEditor.agent", "Agent")}</span>
|
||||
<select
|
||||
value={nodeAgentId}
|
||||
onChange={(e) => updateSelectedData({ config: { agentId: e.target.value || undefined } })}
|
||||
>
|
||||
<option value="">— select agent —</option>
|
||||
<option value="">{t("workflowEditor.selectAgent", "— select agent —")}</option>
|
||||
{nodeAgentStale && (
|
||||
<option value={nodeAgentId}>
|
||||
{t("workflowColumns.agentNotFound", "Agent not found — {{id}}", { id: nodeAgentId })}
|
||||
@@ -3412,12 +3421,12 @@ function InnerEditor({
|
||||
|
||||
{currentExecutor === "skill" && (
|
||||
<label className="wf-field">
|
||||
<span>Skill</span>
|
||||
<span>{t("workflowEditor.skill", "Skill")}</span>
|
||||
<select
|
||||
value={String(selectedNode.data.config?.skillName ?? "")}
|
||||
onChange={(e) => updateSelectedData({ config: { skillName: e.target.value || undefined } })}
|
||||
>
|
||||
<option value="">— select skill —</option>
|
||||
<option value="">{t("workflowEditor.selectSkill", "— select skill —")}</option>
|
||||
{skills.map((s) => (
|
||||
<option key={s.id} value={s.name}>{s.name}</option>
|
||||
))}
|
||||
@@ -3428,26 +3437,26 @@ function InnerEditor({
|
||||
{currentExecutor === "cli" && (
|
||||
<>
|
||||
<label className="wf-field">
|
||||
<span>CLI mode</span>
|
||||
<span>{t("workflowEditor.cliMode", "CLI mode")}</span>
|
||||
<select
|
||||
value={String(selectedNode.data.config?.cliMode ?? "command")}
|
||||
onChange={(e) => updateSelectedData({ config: { cliMode: e.target.value } })}
|
||||
>
|
||||
<option value="command">Command</option>
|
||||
<option value="script">Named script</option>
|
||||
<option value="command">{t("workflowEditor.command", "Command")}</option>
|
||||
<option value="script">{t("workflowEditor.namedScript", "Named script")}</option>
|
||||
</select>
|
||||
</label>
|
||||
{(selectedNode.data.config?.cliMode ?? "command") === "command" ? (
|
||||
<label className="wf-field">
|
||||
<span>Command</span>
|
||||
<span>{t("workflowEditor.command", "Command")}</span>
|
||||
<textarea
|
||||
rows={3}
|
||||
placeholder="npm test -- --runInBand"
|
||||
placeholder={WORKFLOW_CLI_COMMAND_PLACEHOLDER}
|
||||
value={String(selectedNode.data.config?.cliCommand ?? "")}
|
||||
onChange={(e) => updateSelectedData({ config: { cliCommand: e.target.value } })}
|
||||
/>
|
||||
<p className="wf-inspector-note wf-inspector-note--info">
|
||||
Runs an arbitrary command in the task worktree. The first time this exact command runs, the task pauses for your approval. The node prompt is passed via FUSION_NODE_PROMPT.
|
||||
{t("workflowEditor.cliCommandNote", "Runs an arbitrary command in the task worktree. The first time this exact command runs, the task pauses for your approval. The node prompt is passed via FUSION_NODE_PROMPT.")}
|
||||
</p>
|
||||
<label className="wf-field wf-field--checkbox">
|
||||
<input
|
||||
@@ -3455,17 +3464,17 @@ function InnerEditor({
|
||||
checked={selectedNode.data.config?.cliSkipApproval === true}
|
||||
onChange={(e) => updateSelectedData({ config: { cliSkipApproval: e.target.checked } })}
|
||||
/>
|
||||
<span>Skip first-run approval (runs without pausing)</span>
|
||||
<span>{t("workflowEditor.skipFirstRunApproval", "Skip first-run approval (runs without pausing)")}</span>
|
||||
</label>
|
||||
</label>
|
||||
) : (
|
||||
<label className="wf-field">
|
||||
<span>Script name</span>
|
||||
<span>{t("workflowEditor.scriptName", "Script name")}</span>
|
||||
<input
|
||||
value={String(selectedNode.data.config?.scriptName ?? "")}
|
||||
onChange={(e) => updateSelectedData({ config: { scriptName: e.target.value } })}
|
||||
/>
|
||||
<span className="wf-inspector-note">Named script from project settings. The node prompt is passed via FUSION_NODE_PROMPT.</span>
|
||||
<span className="wf-inspector-note">{t("workflowEditor.namedScriptNote", "Named script from project settings. The node prompt is passed via FUSION_NODE_PROMPT.")}</span>
|
||||
</label>
|
||||
)}
|
||||
</>
|
||||
@@ -3554,16 +3563,16 @@ function InnerEditor({
|
||||
checked={Boolean(selectedNode.data.config?.autoApprove)}
|
||||
onChange={(e) => updateSelectedData({ config: { autoApprove: e.target.checked } })}
|
||||
/>
|
||||
<span>Auto-approve requests</span>
|
||||
<span>{t("workflowEditor.autoApproveRequests", "Auto-approve requests")}</span>
|
||||
</label>
|
||||
{Boolean(selectedNode.data.config?.autoApprove) && (
|
||||
<p className="wf-inspector-note">
|
||||
Runs without pausing for approval — e.g. a CLI command executes on its first run without waiting for your sign-off.
|
||||
{t("workflowEditor.autoApproveRequestsNote", "Runs without pausing for approval — e.g. a CLI command executes on its first run without waiting for your sign-off.")}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<label className="wf-field">
|
||||
<span>Max retries</span>
|
||||
<span>{t("workflowEditor.maxRetries", "Max retries")}</span>
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
@@ -3594,11 +3603,11 @@ function InnerEditor({
|
||||
checked={Boolean(selectedNode.data.config?.awaitInput)}
|
||||
onChange={(e) => updateSelectedData({ config: { awaitInput: e.target.checked } })}
|
||||
/>
|
||||
<span>Wait for user input</span>
|
||||
<span>{t("workflowEditor.waitForUserInput", "Wait for user input")}</span>
|
||||
</label>
|
||||
{Boolean(selectedNode.data.config?.awaitInput) && (
|
||||
<p className="wf-inspector-note wf-inspector-note--info">
|
||||
This node pauses the task until you reply in the task's comments and unpause. The Prompt field above is shown to the user as the question.
|
||||
{t("workflowEditor.waitForUserInputNote", "This node pauses the task until you reply in the task's comments and unpause. The Prompt field above is shown to the user as the question.")}
|
||||
</p>
|
||||
)}
|
||||
</>
|
||||
@@ -3606,7 +3615,7 @@ function InnerEditor({
|
||||
|
||||
{selectedNode.data.kind === "script" ? (
|
||||
<label className="wf-field">
|
||||
<span>Script name</span>
|
||||
<span>{t("workflowEditor.scriptName", "Script name")}</span>
|
||||
<input
|
||||
value={String(selectedNode.data.config?.scriptName ?? "")}
|
||||
onChange={(e) => updateSelectedData({ config: { scriptName: e.target.value } })}
|
||||
@@ -4086,7 +4095,7 @@ function InnerEditor({
|
||||
className="wf-code-source"
|
||||
rows={8}
|
||||
spellCheck={false}
|
||||
placeholder={"export default async (ctx) => ({ outcome: \"success\" });"}
|
||||
placeholder={WORKFLOW_CODE_SOURCE_PLACEHOLDER}
|
||||
value={String(selectedNode.data.config?.source ?? "")}
|
||||
onChange={(e) => updateSelectedData({ config: { source: e.target.value } })}
|
||||
/>
|
||||
@@ -4176,7 +4185,7 @@ function InnerEditor({
|
||||
<textarea
|
||||
rows={4}
|
||||
value={String(selectedNode.data.config?.message ?? "")}
|
||||
placeholder="Task {{taskId}} reached {{workflowName}}"
|
||||
placeholder={WORKFLOW_NOTIFY_MESSAGE_PLACEHOLDER}
|
||||
onChange={(e) => updateSelectedData({ config: { message: e.target.value } })}
|
||||
/>
|
||||
</label>
|
||||
@@ -4299,8 +4308,8 @@ function InnerEditor({
|
||||
value={String(selectedEdge.data?.condition ?? "success")}
|
||||
onChange={(e) => updateSelectedEdge({ condition: e.target.value })}
|
||||
>
|
||||
<option value="success">success</option>
|
||||
<option value="failure">failure</option>
|
||||
<option value="success">{t("workflowNodes.conditionSuccess", "success")}</option>
|
||||
<option value="failure">{t("workflowNodes.conditionFailure", "failure")}</option>
|
||||
</select>
|
||||
</label>
|
||||
) : (
|
||||
|
||||
@@ -2,6 +2,11 @@ import "@xyflow/react/dist/style.css";
|
||||
import "./WorkflowResultsTab.css";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
/*
|
||||
FNXC:i18n-Localize 2026-06-20-00:00:
|
||||
FN-6770 requires the workflow/task/setup/PR dashboard cluster to render user-facing copy through locale catalogs so i18n:lint can scan these files again without narrow deferrals.
|
||||
*/
|
||||
import { Check, ChevronDown, ChevronRight, ChevronUp, Maximize2, Pencil, X } from "lucide-react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import remarkGfm from "remark-gfm";
|
||||
@@ -62,10 +67,11 @@ interface WorkflowResultsTabProps {
|
||||
|
||||
/** Extract the user-facing question from a workflow-input paused reason.
|
||||
* Strips the leading "workflow-input:<nodeId>: " prefix if present. */
|
||||
function parseWorkflowInputQuestion(pausedReason?: string): string {
|
||||
if (!pausedReason) return "Reply in the comments and unpause the task to continue.";
|
||||
function parseWorkflowInputQuestion(pausedReason: string | undefined, t: ReturnType<typeof useTranslation>["t"]): string {
|
||||
const fallback = t("app:workflow.replyInComments", "Reply in the comments and unpause the task to continue.");
|
||||
if (!pausedReason) return fallback;
|
||||
const match = /^workflow-input:[^:]+:\s*(.*)$/s.exec(pausedReason);
|
||||
if (match) return match[1].trim() || "Reply in the comments and unpause the task to continue.";
|
||||
if (match) return match[1].trim() || fallback;
|
||||
return pausedReason;
|
||||
}
|
||||
|
||||
@@ -89,15 +95,15 @@ interface WorkflowStepOption {
|
||||
function getStatusLabel(status: WorkflowStepResult["status"], t: ReturnType<typeof useTranslation>["t"]): string {
|
||||
switch (status) {
|
||||
case "passed":
|
||||
return t("workflow.statusPassed", "Passed");
|
||||
return t("app:workflow.statusPassed", "Passed");
|
||||
case "failed":
|
||||
return t("workflow.statusFailed", "Failed");
|
||||
return t("app:workflow.statusFailed", "Failed");
|
||||
case "advisory_failure":
|
||||
return t("workflow.statusAdvisory", "Advisory failure");
|
||||
return t("app:workflow.statusAdvisory", "Advisory failure");
|
||||
case "skipped":
|
||||
return t("workflow.statusSkipped", "Skipped");
|
||||
return t("app:workflow.statusSkipped", "Skipped");
|
||||
case "pending":
|
||||
return t("workflow.statusRunning", "Running…");
|
||||
return t("app:workflow.statusRunning", "Running…");
|
||||
default:
|
||||
return status;
|
||||
}
|
||||
@@ -135,7 +141,7 @@ function phaseBadge(phase: "pre-merge" | "post-merge", id: string, prefix: strin
|
||||
className={`phase-badge ${phaseClass}`}
|
||||
data-testid={`${prefix}-${id}`}
|
||||
>
|
||||
{phase === "post-merge" ? t("workflow.postMerge", "Post-merge") : t("workflow.preMerge", "Pre-merge")}
|
||||
{phase === "post-merge" ? t("app:workflow.postMerge", "Post-merge") : t("app:workflow.preMerge", "Pre-merge")}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
@@ -145,9 +151,9 @@ function getWorkflowName(
|
||||
workflows: WorkflowDefinition[],
|
||||
t: ReturnType<typeof useTranslation>["t"],
|
||||
): string {
|
||||
if (!selectedWorkflowId) return t("workflow.defaultWorkflow", "Default");
|
||||
if (!selectedWorkflowId) return t("app:workflow.defaultWorkflow", "Default");
|
||||
const match = workflows.find((workflow) => workflow.id === selectedWorkflowId);
|
||||
return match?.name || t("workflow.customWorkflowFallback", "Custom workflow");
|
||||
return match?.name || t("app:workflow.customWorkflowFallback", "Custom workflow");
|
||||
}
|
||||
|
||||
function getAggregateWorkflowResult(
|
||||
@@ -155,18 +161,18 @@ function getAggregateWorkflowResult(
|
||||
t: ReturnType<typeof useTranslation>["t"],
|
||||
): { label: string; badgeClass: string; testId: string } {
|
||||
if (results.some((result) => result.status === "failed")) {
|
||||
return { label: t("workflow.statusFailed", "Failed"), badgeClass: "workflow-result-badge--failed", testId: "failed" };
|
||||
return { label: t("app:workflow.statusFailed", "Failed"), badgeClass: "workflow-result-badge--failed", testId: "failed" };
|
||||
}
|
||||
if (results.some((result) => result.status === "advisory_failure")) {
|
||||
return { label: t("workflow.statusAdvisory", "Advisory"), badgeClass: "workflow-result-badge--advisory_failure", testId: "advisory" };
|
||||
return { label: t("app:workflow.aggregateAdvisory", "Advisory"), badgeClass: "workflow-result-badge--advisory_failure", testId: "advisory" };
|
||||
}
|
||||
if (results.some((result) => result.status === "pending")) {
|
||||
return { label: t("workflow.aggregateInProgress", "In progress"), badgeClass: "workflow-result-badge--pending", testId: "pending" };
|
||||
return { label: t("app:workflow.aggregateInProgress", "In progress"), badgeClass: "workflow-result-badge--pending", testId: "pending" };
|
||||
}
|
||||
if (results.length === 0) {
|
||||
return { label: t("workflow.aggregateNoResults", "No results"), badgeClass: "workflow-result-badge--skipped", testId: "no-results" };
|
||||
return { label: t("app:workflow.aggregateNoResults", "No results"), badgeClass: "workflow-result-badge--skipped", testId: "no-results" };
|
||||
}
|
||||
return { label: t("workflow.aggregateAllPassed", "All passed"), badgeClass: "workflow-result-badge--passed", testId: "passed" };
|
||||
return { label: t("app:workflow.aggregateAllPassed", "All passed"), badgeClass: "workflow-result-badge--passed", testId: "passed" };
|
||||
}
|
||||
|
||||
function getExecutionPhase(
|
||||
@@ -177,13 +183,13 @@ function getExecutionPhase(
|
||||
t: ReturnType<typeof useTranslation>["t"],
|
||||
): { label: string; badgeClass: string; testId: string } {
|
||||
if (taskStatus === "awaiting-user-input") {
|
||||
return { label: t("workflow.executionAwaitingInput", "Awaiting input"), badgeClass: "workflow-result-badge--pending", testId: "awaiting-input" };
|
||||
return { label: t("app:workflow.executionAwaitingInput", "Awaiting input"), badgeClass: "workflow-result-badge--pending", testId: "awaiting-input" };
|
||||
}
|
||||
if (taskStatus === "awaiting-cli-approval") {
|
||||
return { label: t("workflow.executionAwaitingCliApproval", "Awaiting CLI approval"), badgeClass: "workflow-result-badge--pending", testId: "awaiting-cli-approval" };
|
||||
return { label: t("app:workflow.executionAwaitingCliApproval", "Awaiting CLI approval"), badgeClass: "workflow-result-badge--pending", testId: "awaiting-cli-approval" };
|
||||
}
|
||||
if (taskStatus === "paused" || taskPausedReason) {
|
||||
return { label: t("workflow.executionPaused", "Paused"), badgeClass: "workflow-result-badge--pending", testId: "paused" };
|
||||
return { label: t("app:workflow.executionPaused", "Paused"), badgeClass: "workflow-result-badge--pending", testId: "paused" };
|
||||
}
|
||||
|
||||
const pendingResult = results.find((result) => result.status === "pending");
|
||||
@@ -191,8 +197,8 @@ function getExecutionPhase(
|
||||
const isPostMerge = (pendingResult.phase || "pre-merge") === "post-merge";
|
||||
return {
|
||||
label: isPostMerge
|
||||
? t("workflow.executionPostMerge", "Post-merge steps running")
|
||||
: t("workflow.executionPreMerge", "Pre-merge steps running"),
|
||||
? t("app:workflow.executionPostMerge", "Post-merge steps running")
|
||||
: t("app:workflow.executionPreMerge", "Pre-merge steps running"),
|
||||
badgeClass: "workflow-result-badge--pending",
|
||||
testId: isPostMerge ? "post-merge" : "pre-merge",
|
||||
};
|
||||
@@ -200,14 +206,14 @@ function getExecutionPhase(
|
||||
|
||||
const hasTerminalResults = results.length > 0 && results.every((result) => ["passed", "failed", "advisory_failure", "skipped"].includes(result.status));
|
||||
if (hasTerminalResults || taskStatus === "done" || task?.column === "done" || task?.column === "in-review") {
|
||||
return { label: t("workflow.executionCompleted", "Completed"), badgeClass: "workflow-result-badge--passed", testId: "completed" };
|
||||
return { label: t("app:workflow.executionCompleted", "Completed"), badgeClass: "workflow-result-badge--passed", testId: "completed" };
|
||||
}
|
||||
|
||||
return { label: t("workflow.executionNotStarted", "Not started"), badgeClass: "workflow-result-badge--pending", testId: "not-started" };
|
||||
return { label: t("app:workflow.executionNotStarted", "Not started"), badgeClass: "workflow-result-badge--pending", testId: "not-started" };
|
||||
}
|
||||
|
||||
function formatModelValue(selection: { provider?: string; modelId?: string } | undefined): string {
|
||||
if (!selection?.provider || !selection.modelId) return "Default";
|
||||
function formatModelValue(selection: { provider?: string; modelId?: string } | undefined, t: ReturnType<typeof useTranslation>["t"]): string {
|
||||
if (!selection?.provider || !selection.modelId) return t("app:workflow.modelDefault", "Default");
|
||||
return `${selection.provider}/${selection.modelId}`;
|
||||
}
|
||||
|
||||
@@ -245,7 +251,7 @@ function LiveAgentLogOutput({
|
||||
if (stepEntries.length === 0) {
|
||||
return (
|
||||
<div className="workflow-live-log" data-testid={`workflow-live-log-${stepId}`}>
|
||||
<div className="workflow-live-log-empty">{t("workflow.waitingForOutput", "Waiting for agent output…")}</div>
|
||||
<div className="workflow-live-log-empty">{t("app:workflow.waitingForOutput", "Waiting for agent output…")}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -570,7 +576,7 @@ export function WorkflowResultsTab({
|
||||
return {
|
||||
id: stepId,
|
||||
name: stepInfo?.name || stepId,
|
||||
description: stepInfo?.description || t("workflow.stepDefinitionNotFound", "Step definition not found."),
|
||||
description: stepInfo?.description || t("app:workflow.stepDefinitionNotFound", "Step definition not found."),
|
||||
phase: stepInfo?.phase || "pre-merge",
|
||||
} as WorkflowStepOption;
|
||||
});
|
||||
@@ -595,7 +601,7 @@ export function WorkflowResultsTab({
|
||||
<div className="workflow-results-editor" data-testid="workflow-steps-editor">
|
||||
<div className="workflow-steps-section">
|
||||
<small className="workflow-steps-description">
|
||||
{t("workflow.selectStepsDescription", "Select steps to run after task implementation completes")}
|
||||
{t("app:workflow.selectStepsDescription", "Select steps to run after task implementation completes")}
|
||||
</small>
|
||||
<div className="workflow-steps-list">
|
||||
{workflowStepOptions.map((step) => (
|
||||
@@ -625,7 +631,7 @@ export function WorkflowResultsTab({
|
||||
|
||||
{selectedWorkflowSteps.length > 1 && (
|
||||
<div className="workflow-step-order" data-testid="workflow-step-order">
|
||||
<small className="workflow-step-order-label">{t("workflow.executionOrder", "Execution order:")}</small>
|
||||
<small className="workflow-step-order-label">{t("app:workflow.executionOrder", "Execution order:")}</small>
|
||||
{selectedWorkflowSteps.map((stepId, index) => {
|
||||
const stepInfo = workflowStepLookup.get(stepId);
|
||||
return (
|
||||
@@ -639,7 +645,7 @@ export function WorkflowResultsTab({
|
||||
onClick={() => moveWorkflowStepUp(index)}
|
||||
disabled={index === 0}
|
||||
data-testid={`workflow-step-move-up-${stepId}`}
|
||||
title={t("workflow.moveUp", "Move up")}
|
||||
title={t("app:workflow.moveUp", "Move up")}
|
||||
>
|
||||
<ChevronUp />
|
||||
</button>
|
||||
@@ -649,7 +655,7 @@ export function WorkflowResultsTab({
|
||||
onClick={() => moveWorkflowStepDown(index)}
|
||||
disabled={index === selectedWorkflowSteps.length - 1}
|
||||
data-testid={`workflow-step-move-down-${stepId}`}
|
||||
title={t("workflow.moveDown", "Move down")}
|
||||
title={t("app:workflow.moveDown", "Move down")}
|
||||
>
|
||||
<ChevronDown />
|
||||
</button>
|
||||
@@ -658,7 +664,7 @@ export function WorkflowResultsTab({
|
||||
className="btn btn-icon btn-sm"
|
||||
onClick={() => removeWorkflowStep(stepId)}
|
||||
data-testid={`workflow-step-remove-${stepId}`}
|
||||
title={t("workflow.remove", "Remove")}
|
||||
title={t("app:workflow.remove", "Remove")}
|
||||
>
|
||||
<X />
|
||||
</button>
|
||||
@@ -677,7 +683,7 @@ export function WorkflowResultsTab({
|
||||
return (
|
||||
<div className="workflow-results-loading" data-testid="workflow-results-loading">
|
||||
<div className="workflow-results-spinner" />
|
||||
<span>{t("workflow.loadingResults", "Loading workflow results…")}</span>
|
||||
<span>{t("app:workflow.loadingResults", "Loading workflow results…")}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -685,9 +691,9 @@ export function WorkflowResultsTab({
|
||||
if (!hasResults) {
|
||||
return (
|
||||
<div className="workflow-results-empty" data-testid="workflow-results-empty">
|
||||
<p>{t("workflow.noStepsConfigured", "No workflow steps configured for this task.")}</p>
|
||||
<p>{t("app:workflow.noStepsConfigured", "No workflow steps configured for this task.")}</p>
|
||||
<p className="workflow-results-empty-hint">
|
||||
{t("workflow.stepsExplanation", "Pre-merge steps run after implementation, before merge. Post-merge steps run after merge succeeds.")}
|
||||
{t("app:workflow.stepsExplanation", "Pre-merge steps run after implementation, before merge. Post-merge steps run after merge succeeds.")}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
@@ -699,26 +705,26 @@ export function WorkflowResultsTab({
|
||||
const skipped = results.filter((r) => r.status === "skipped").length;
|
||||
const pending = results.filter((r) => r.status === "pending").length;
|
||||
|
||||
const summaryParts: string[] = [t("workflow.summaryStepCount", { count: results.length, defaultValue_one: "{{count}} step", defaultValue_other: "{{count}} steps" })];
|
||||
if (passed > 0) summaryParts.push(t("workflow.summaryPassed", "{{count}} passed", { count: passed }));
|
||||
if (failed > 0) summaryParts.push(t("workflow.summaryFailed", "{{count}} failed", { count: failed }));
|
||||
if (advisoryFailures.length > 0) summaryParts.push(t("workflow.summaryAdvisory", "{{count}} advisory", { count: advisoryFailures.length }));
|
||||
if (skipped > 0) summaryParts.push(t("workflow.summarySkipped", "{{count}} skipped", { count: skipped }));
|
||||
if (pending > 0) summaryParts.push(t("workflow.summaryRunning", "{{count}} running", { count: pending }));
|
||||
const summaryParts: string[] = [t("app:workflow.summaryStepCount", { count: results.length, defaultValue_one: "{{count}} step", defaultValue_other: "{{count}} steps" })];
|
||||
if (passed > 0) summaryParts.push(t("app:workflow.summaryPassed", "{{count}} passed", { count: passed }));
|
||||
if (failed > 0) summaryParts.push(t("app:workflow.summaryFailed", "{{count}} failed", { count: failed }));
|
||||
if (advisoryFailures.length > 0) summaryParts.push(t("app:workflow.summaryAdvisory", "{{count}} advisory", { count: advisoryFailures.length }));
|
||||
if (skipped > 0) summaryParts.push(t("app:workflow.summarySkipped", "{{count}} skipped", { count: skipped }));
|
||||
if (pending > 0) summaryParts.push(t("app:workflow.summaryRunning", "{{count}} running", { count: pending }));
|
||||
|
||||
return (
|
||||
<div className="workflow-results-list" data-testid="workflow-results-list">
|
||||
<div className="workflow-results-summary-bar" data-testid="workflow-results-summary">
|
||||
{summaryParts.join(t("workflow.summarySeparator", " · "))}
|
||||
{summaryParts.join(t("app:workflow.summarySeparator", " · "))}
|
||||
</div>
|
||||
{advisoryFailures.length > 0 && (
|
||||
<div className="workflow-polish-notes" data-testid="workflow-polish-notes">
|
||||
<h4>{t("workflow.polishNotes", "Polish notes")}</h4>
|
||||
<p>{t("workflow.advisoryExplanation", "Advisory workflow steps flagged non-blocking improvements:")}</p>
|
||||
<h4>{t("app:workflow.polishNotes", "Polish notes")}</h4>
|
||||
<p>{t("app:workflow.advisoryExplanation", "Advisory workflow steps flagged non-blocking improvements:")}</p>
|
||||
<ul>
|
||||
{advisoryFailures.map((result, index) => (
|
||||
<li key={`advisory-${result.workflowStepId}-${index}`}>
|
||||
<strong>{result.workflowStepName}:</strong> {result.output || t("workflow.needsReview", "Needs follow-up review.")}
|
||||
<strong>{result.workflowStepName}:</strong> {result.output || t("app:workflow.needsReview", "Needs follow-up review.")}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
@@ -758,7 +764,7 @@ export function WorkflowResultsTab({
|
||||
|
||||
{result.notes && result.status !== "pending" && (
|
||||
<div className="workflow-result-notes" data-testid={`workflow-result-notes-${result.workflowStepId}`}>
|
||||
<span className="workflow-result-notes-label">{t("workflow.notes", "Notes:")} </span>
|
||||
<span className="workflow-result-notes-label">{t("app:workflow.notes", "Notes:")} </span>
|
||||
<ReactMarkdown remarkPlugins={[remarkGfm]} components={markdownComponents}>
|
||||
{result.notes}
|
||||
</ReactMarkdown>
|
||||
@@ -767,7 +773,7 @@ export function WorkflowResultsTab({
|
||||
|
||||
<div className="workflow-result-meta">
|
||||
{result.startedAt && (
|
||||
<span className="workflow-result-timestamp">{t("workflow.started", "Started:")} {formatTimestamp(result.startedAt)}</span>
|
||||
<span className="workflow-result-timestamp">{t("app:workflow.started", "Started:")} {formatTimestamp(result.startedAt)}</span>
|
||||
)}
|
||||
{result.completedAt && (
|
||||
<span className="workflow-result-duration">{formatDuration(result.startedAt, result.completedAt)}</span>
|
||||
@@ -785,14 +791,14 @@ export function WorkflowResultsTab({
|
||||
) : result.output ? (
|
||||
<div className="workflow-result-output-section">
|
||||
<div className="workflow-result-output-header">
|
||||
<span className="workflow-result-output-label">{t("workflow.output", "Output:")} </span>
|
||||
<span className="workflow-result-output-label">{t("app:workflow.output", "Output:")} </span>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-sm workflow-result-toggle"
|
||||
onClick={() => toggleOutput(result.workflowStepId)}
|
||||
data-testid={`workflow-result-toggle-${result.workflowStepId}`}
|
||||
>
|
||||
{isExpanded ? t("workflow.hideOutput", "Hide output") : t("workflow.showOutput", "Show output")}
|
||||
{isExpanded ? t("app:workflow.hideOutput", "Hide output") : t("app:workflow.showOutput", "Show output")}
|
||||
</button>
|
||||
{!isExpanded && (
|
||||
<span
|
||||
@@ -809,16 +815,16 @@ export function WorkflowResultsTab({
|
||||
className="btn btn-sm workflow-result-mode-toggle"
|
||||
onClick={() => toggleRenderMode(result.workflowStepId)}
|
||||
data-testid={`workflow-result-mode-toggle-${result.workflowStepId}`}
|
||||
title={(renderModes[result.workflowStepId] ?? "markdown") === "markdown" ? t("workflow.switchToPlain", "Switch to plain text") : t("workflow.switchToMarkdown", "Switch to markdown")}
|
||||
title={(renderModes[result.workflowStepId] ?? "markdown") === "markdown" ? t("app:workflow.switchToPlain", "Switch to plain text") : t("app:workflow.switchToMarkdown", "Switch to markdown")}
|
||||
>
|
||||
{(renderModes[result.workflowStepId] ?? "markdown") === "markdown" ? t("workflow.markdown", "Markdown") : t("workflow.plain", "Plain")}
|
||||
{(renderModes[result.workflowStepId] ?? "markdown") === "markdown" ? t("app:workflow.markdown", "Markdown") : t("app:workflow.plain", "Plain")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="btn btn-icon btn-sm workflow-result-expand-toggle"
|
||||
onClick={() => openExpandedView(result.workflowStepId)}
|
||||
data-testid={`workflow-result-expand-${result.workflowStepId}`}
|
||||
title={t("workflow.expandOutput", "Expand output")}
|
||||
title={t("app:workflow.expandOutput", "Expand output")}
|
||||
>
|
||||
<Maximize2 size={12} />
|
||||
</button>
|
||||
@@ -858,18 +864,18 @@ export function WorkflowResultsTab({
|
||||
className="btn btn-sm workflow-results-edit-toggle"
|
||||
onClick={() => setIsEditing((prev) => !prev)}
|
||||
data-testid="workflow-steps-edit-toggle"
|
||||
aria-label={isEditing ? t("workflow.doneEditingAriaLabel", "Done editing workflow steps") : t("workflow.editAriaLabel", "Edit workflow steps")}
|
||||
title={isEditing ? t("workflow.done", "Done") : t("workflow.edit", "Edit")}
|
||||
aria-label={isEditing ? t("app:workflow.doneEditingAriaLabel", "Done editing workflow steps") : t("app:workflow.editAriaLabel", "Edit workflow steps")}
|
||||
title={isEditing ? t("app:workflow.done", "Done") : t("app:workflow.edit", "Edit")}
|
||||
>
|
||||
{isEditing ? (
|
||||
<>
|
||||
<Check size={14} />
|
||||
{t("workflow.done", "Done")}
|
||||
{t("app:workflow.done", "Done")}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Pencil size={14} />
|
||||
{t("workflow.edit", "Edit")}
|
||||
{t("app:workflow.edit", "Edit")}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
@@ -891,7 +897,7 @@ export function WorkflowResultsTab({
|
||||
setInputText("");
|
||||
setSubmitted(true);
|
||||
} catch (err) {
|
||||
setResumeError(getErrorMessage(err) || "Failed to resume task");
|
||||
setResumeError(getErrorMessage(err) || t("app:workflow.resumeTaskError", "Failed to resume task"));
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
@@ -905,7 +911,7 @@ export function WorkflowResultsTab({
|
||||
await approveTaskWorkflowCli(taskId, projectId);
|
||||
setSubmitted(true);
|
||||
} catch (err) {
|
||||
setResumeError(getErrorMessage(err) || "Failed to approve command");
|
||||
setResumeError(getErrorMessage(err) || t("app:workflow.approveCommandError", "Failed to approve command"));
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
@@ -915,16 +921,16 @@ export function WorkflowResultsTab({
|
||||
<div className="workflow-results-tab" data-task-id={taskId}>
|
||||
{isAwaitingInput && (
|
||||
<div className="workflow-input-banner" role="alert">
|
||||
<strong>Waiting for your input</strong>
|
||||
<span>{parseWorkflowInputQuestion(taskPausedReason)}</span>
|
||||
<strong>{t("app:workflow.awaitingInputTitle", "Waiting for your input")}</strong>
|
||||
<span>{parseWorkflowInputQuestion(taskPausedReason, t)}</span>
|
||||
{submitted ? (
|
||||
<span className="workflow-input-resuming">Resuming…</span>
|
||||
<span className="workflow-input-resuming">{t("app:workflow.resuming", "Resuming…")}</span>
|
||||
) : (
|
||||
<div className="workflow-input-actions">
|
||||
<textarea
|
||||
className="workflow-input-textarea"
|
||||
rows={3}
|
||||
placeholder="Type your reply…"
|
||||
placeholder={t("app:workflow.inputPlaceholder", "Type your reply…")}
|
||||
value={inputText}
|
||||
onChange={(e) => setInputText(e.target.value)}
|
||||
disabled={submitting}
|
||||
@@ -935,7 +941,7 @@ export function WorkflowResultsTab({
|
||||
onClick={handleSubmitInput}
|
||||
disabled={submitting || !inputText.trim()}
|
||||
>
|
||||
{submitting ? "Submitting…" : "Submit & resume"}
|
||||
{submitting ? t("app:workflow.submitting", "Submitting…") : t("app:workflow.submitAndResume", "Submit & resume")}
|
||||
</button>
|
||||
{resumeError && (
|
||||
<span className="workflow-input-error" role="alert">{resumeError}</span>
|
||||
@@ -946,13 +952,13 @@ export function WorkflowResultsTab({
|
||||
)}
|
||||
{isAwaitingCliApproval && (
|
||||
<div className="workflow-input-banner workflow-input-banner--approval" role="alert">
|
||||
<strong>Approve CLI command?</strong>
|
||||
<strong>{t("app:workflow.cliApprovalTitle", "Approve CLI command?")}</strong>
|
||||
<span className="workflow-input-approval-warning">
|
||||
This command will run in the task worktree. Approving trusts this exact command for future runs.
|
||||
{t("app:workflow.cliApprovalWarning", "This command will run in the task worktree. Approving trusts this exact command for future runs.")}
|
||||
</span>
|
||||
<pre className="workflow-input-approval-command"><code>{parseCliApprovalCommand(taskPausedReason)}</code></pre>
|
||||
{submitted ? (
|
||||
<span className="workflow-input-resuming">Resuming…</span>
|
||||
<span className="workflow-input-resuming">{t("app:workflow.resuming", "Resuming…")}</span>
|
||||
) : (
|
||||
<div className="workflow-input-actions">
|
||||
<button
|
||||
@@ -961,9 +967,9 @@ export function WorkflowResultsTab({
|
||||
onClick={handleApproveCli}
|
||||
disabled={submitting}
|
||||
>
|
||||
{submitting ? "Approving…" : "Approve & run"}
|
||||
{submitting ? t("app:workflow.approving", "Approving…") : t("app:workflow.approveAndRun", "Approve & run")}
|
||||
</button>
|
||||
<span className="workflow-input-keep-paused">To reject, keep the task paused and do not approve.</span>
|
||||
<span className="workflow-input-keep-paused">{t("app:workflow.cliApprovalRejectHint", "To reject, keep the task paused and do not approve.")}</span>
|
||||
{resumeError && (
|
||||
<span className="workflow-input-error" role="alert">{resumeError}</span>
|
||||
)}
|
||||
@@ -973,28 +979,28 @@ export function WorkflowResultsTab({
|
||||
)}
|
||||
<section className="card workflow-state-summary" data-testid="workflow-state-summary">
|
||||
<div className="workflow-state-summary__header">
|
||||
<h4>{t("workflow.overview", "Workflow overview")}</h4>
|
||||
<h4>{t("app:workflow.overview", "Workflow overview")}</h4>
|
||||
</div>
|
||||
<div className="workflow-state-summary__grid">
|
||||
<div className="workflow-state-summary__item" data-testid="workflow-state-summary-name">
|
||||
<span className="workflow-state-summary__label">{t("workflow.workflowName", "Workflow")}</span>
|
||||
<span className="workflow-state-summary__label">{t("app:workflow.workflowName", "Workflow")}</span>
|
||||
<span className="workflow-state-summary__value">{workflowName}</span>
|
||||
</div>
|
||||
<div className="workflow-state-summary__item" data-testid="workflow-state-summary-phase">
|
||||
<span className="workflow-state-summary__label">{t("workflow.executionPhase", "Execution phase")}</span>
|
||||
<span className="workflow-state-summary__label">{t("app:workflow.executionPhase", "Execution phase")}</span>
|
||||
<span className={`workflow-result-badge ${executionPhase.badgeClass}`} data-testid={`workflow-phase-badge-${executionPhase.testId}`}>
|
||||
{executionPhase.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="workflow-state-summary__item" data-testid="workflow-state-summary-aggregate">
|
||||
<span className="workflow-state-summary__label">{t("workflow.aggregateResult", "Aggregate result")}</span>
|
||||
<span className="workflow-state-summary__label">{t("app:workflow.aggregateResult", "Aggregate result")}</span>
|
||||
<span className={`workflow-result-badge ${aggregateResult.badgeClass}`} data-testid={`workflow-aggregate-badge-${aggregateResult.testId}`}>
|
||||
{aggregateResult.label}
|
||||
</span>
|
||||
</div>
|
||||
<div className="workflow-state-summary__item" data-testid="workflow-state-summary-count">
|
||||
<span className="workflow-state-summary__label">{t("workflow.stepProgress", "Step count")}</span>
|
||||
<span className="workflow-state-summary__value">{t("workflow.stepProgressValue", "{{completed}} of {{total}} steps completed", { completed: completedStepCount, total: results.length })}</span>
|
||||
<span className="workflow-state-summary__label">{t("app:workflow.stepProgress", "Step count")}</span>
|
||||
<span className="workflow-state-summary__value">{t("app:workflow.stepProgressValue", "{{completed}} of {{total}} steps completed", { completed: completedStepCount, total: results.length })}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1008,19 +1014,19 @@ export function WorkflowResultsTab({
|
||||
>
|
||||
<span className="workflow-disclosure__title">
|
||||
{graphExpanded ? <ChevronDown aria-hidden /> : <ChevronRight aria-hidden />}
|
||||
{t("workflow.graph", "Workflow graph")}
|
||||
{t("app:workflow.graph", "Workflow graph")}
|
||||
</span>
|
||||
</button>
|
||||
{graphExpanded && (
|
||||
<div className="workflow-disclosure__content">
|
||||
{!selectedWorkflowId ? (
|
||||
<p className="workflow-disclosure__empty" data-testid="workflow-graph-empty">
|
||||
{t("workflow.noWorkflowAssigned", "No workflow assigned")}
|
||||
{t("app:workflow.noWorkflowAssigned", "No workflow assigned")}
|
||||
</p>
|
||||
) : workflowGraphLoading && !graphWorkflow ? (
|
||||
<div className="workflow-results-loading" data-testid="workflow-graph-loading">
|
||||
<div className="workflow-results-spinner" />
|
||||
<span>{t("workflow.loadingGraph", "Loading workflow graph…")}</span>
|
||||
<span>{t("app:workflow.loadingGraph", "Loading workflow graph…")}</span>
|
||||
</div>
|
||||
) : graphFlow ? (
|
||||
<div className="workflow-graph-preview" data-testid="workflow-graph-preview">
|
||||
@@ -1042,7 +1048,7 @@ export function WorkflowResultsTab({
|
||||
</div>
|
||||
) : (
|
||||
<p className="workflow-disclosure__empty" data-testid="workflow-graph-unavailable">
|
||||
{t("workflow.graphUnavailable", "Workflow graph unavailable")}
|
||||
{t("app:workflow.graphUnavailable", "Workflow graph unavailable")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
@@ -1051,7 +1057,7 @@ export function WorkflowResultsTab({
|
||||
|
||||
<section className="card workflow-management" data-testid="workflow-management-section">
|
||||
<div className="workflow-management__header">
|
||||
<h4>{t("workflow.workflowName", "Workflow")}</h4>
|
||||
<h4>{t("app:workflow.workflowName", "Workflow")}</h4>
|
||||
{canEdit && selectedWorkflowId && onEditWorkflow && (
|
||||
<button
|
||||
type="button"
|
||||
@@ -1060,7 +1066,7 @@ export function WorkflowResultsTab({
|
||||
data-testid="workflow-edit-button"
|
||||
>
|
||||
<Pencil aria-hidden />
|
||||
{t("workflow.editWorkflow", "Edit workflow")}
|
||||
{t("app:workflow.editWorkflow", "Edit workflow")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -1068,7 +1074,7 @@ export function WorkflowResultsTab({
|
||||
value={selectedWorkflowId}
|
||||
onChange={handleWorkflowSelect}
|
||||
projectId={projectId}
|
||||
label="Custom workflow"
|
||||
label={t("app:workflow.customWorkflowLabel", "Custom workflow")}
|
||||
disabled={!canEdit}
|
||||
/>
|
||||
</section>
|
||||
@@ -1082,16 +1088,16 @@ export function WorkflowResultsTab({
|
||||
>
|
||||
<span className="workflow-disclosure__title">
|
||||
{modelSettingsExpanded ? <ChevronDown aria-hidden /> : <ChevronRight aria-hidden />}
|
||||
{t("workflow.modelSettings", "Model settings")}
|
||||
{t("app:workflow.modelSettings", "Model settings")}
|
||||
</span>
|
||||
</button>
|
||||
{modelSettingsExpanded && (
|
||||
<div className="workflow-disclosure__content workflow-state-summary__grid" data-testid="workflow-model-settings-content">
|
||||
{[
|
||||
{ key: "executor", label: t("models.targetLabels.executor", "Executor"), value: formatModelValue(effectiveExecutor), provider: effectiveExecutor?.provider },
|
||||
{ key: "reviewer", label: t("models.targetLabels.validator", "Reviewer"), value: formatModelValue(effectiveValidator), provider: effectiveValidator?.provider },
|
||||
{ key: "planning", label: t("models.targetLabels.planning", "Planning"), value: formatModelValue(effectivePlanning), provider: effectivePlanning?.provider },
|
||||
{ key: "thinking", label: t("workflow.thinkingLevel", "Thinking level"), value: task?.thinkingLevel || "Default" },
|
||||
{ key: "executor", label: t("models.targetLabels.executor", "Executor"), value: formatModelValue(effectiveExecutor, t), provider: effectiveExecutor?.provider },
|
||||
{ key: "reviewer", label: t("models.targetLabels.validator", "Reviewer"), value: formatModelValue(effectiveValidator, t), provider: effectiveValidator?.provider },
|
||||
{ key: "planning", label: t("models.targetLabels.planning", "Planning"), value: formatModelValue(effectivePlanning, t), provider: effectivePlanning?.provider },
|
||||
{ key: "thinking", label: t("app:workflow.thinkingLevel", "Thinking level"), value: task?.thinkingLevel || "Default" },
|
||||
].map((item) => (
|
||||
<div className="workflow-state-summary__item" key={item.key} data-testid={`workflow-model-setting-${item.key}`}>
|
||||
<span className="workflow-state-summary__label">{item.label}</span>
|
||||
@@ -1108,9 +1114,9 @@ export function WorkflowResultsTab({
|
||||
<div className="workflow-configured-steps" data-testid="workflow-configured-steps">
|
||||
<div className="workflow-configured-header" data-testid="workflow-configured-header">
|
||||
<div className="workflow-configured-title-row">
|
||||
<h4>{t("workflow.configuredSteps", "Configured Workflow Steps")}</h4>
|
||||
<h4>{t("app:workflow.configuredSteps", "Configured Workflow Steps")}</h4>
|
||||
<span className="workflow-configured-count" data-testid="workflow-configured-count">
|
||||
{t("workflow.stepCount", { count: configuredSteps.length, defaultValue_one: "{{count}} step", defaultValue_other: "{{count}} steps" })}
|
||||
{t("app:workflow.stepCount", { count: configuredSteps.length, defaultValue_one: "{{count}} step", defaultValue_other: "{{count}} steps" })}
|
||||
</span>
|
||||
</div>
|
||||
{editButton}
|
||||
@@ -1133,7 +1139,7 @@ export function WorkflowResultsTab({
|
||||
</div>
|
||||
|
||||
<p className="workflow-results-empty-hint">
|
||||
{t("workflow.stepsExplanation", "Pre-merge steps run after implementation, before merge. Post-merge steps run after merge succeeds.")}
|
||||
{t("app:workflow.stepsExplanation", "Pre-merge steps run after implementation, before merge. Post-merge steps run after merge succeeds.")}
|
||||
</p>
|
||||
|
||||
{renderEditor()}
|
||||
@@ -1142,7 +1148,7 @@ export function WorkflowResultsTab({
|
||||
<>
|
||||
{showEditHeaderForResults && (
|
||||
<div className="workflow-results-edit-header" data-testid="workflow-results-edit-header">
|
||||
<h4>{t("workflow.steps", "Workflow Steps")}</h4>
|
||||
<h4>{t("app:workflow.steps", "Workflow Steps")}</h4>
|
||||
{editButton}
|
||||
</div>
|
||||
)}
|
||||
@@ -1179,9 +1185,9 @@ export function WorkflowResultsTab({
|
||||
className="btn btn-sm workflow-result-mode-toggle"
|
||||
onClick={() => toggleRenderMode(result.workflowStepId)}
|
||||
data-testid="workflow-output-modal-mode-toggle"
|
||||
title={renderMode === "markdown" ? t("workflow.switchToPlain", "Switch to plain text") : t("workflow.switchToMarkdown", "Switch to markdown")}
|
||||
title={renderMode === "markdown" ? t("app:workflow.switchToPlain", "Switch to plain text") : t("app:workflow.switchToMarkdown", "Switch to markdown")}
|
||||
>
|
||||
{renderMode === "markdown" ? t("workflow.markdown", "Markdown") : t("workflow.plain", "Plain")}
|
||||
{renderMode === "markdown" ? t("app:workflow.markdown", "Markdown") : t("app:workflow.plain", "Plain")}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
|
||||
@@ -8,6 +8,11 @@ import { fetchWorkflow, fetchWorkflows, fetchProjectDefaultWorkflow, setProjectD
|
||||
import type { ToastType } from "../hooks/useToast";
|
||||
import { useConfirm } from "../hooks/useConfirm";
|
||||
|
||||
/*
|
||||
FNXC:i18n-Localize 2026-06-20-00:00:
|
||||
FN-6770 localizes this workflow surface through t() and authored en catalog keys so hardcoded user-facing copy does not need a lint.ignore deferral.
|
||||
*/
|
||||
|
||||
interface WorkflowSelectorProps {
|
||||
/** Currently selected workflow id, or null for none. */
|
||||
value: string | null;
|
||||
@@ -63,7 +68,7 @@ export function WorkflowSelector({
|
||||
})
|
||||
.catch((err) => {
|
||||
if (!cancelled) setWorkflows([]);
|
||||
addToast?.(getErrorMessage(err) || "Failed to load workflows", "error");
|
||||
addToast?.(getErrorMessage(err) || t("workflowSelector.loadFailed", "Failed to load workflows"), "error");
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoading(false);
|
||||
@@ -93,7 +98,7 @@ export function WorkflowSelector({
|
||||
try {
|
||||
await onChange(workflowId);
|
||||
} catch (err) {
|
||||
addToast?.(getErrorMessage(err) || "Failed to apply workflow", "error");
|
||||
addToast?.(getErrorMessage(err) || t("workflowSelector.applyFailed", "Failed to apply workflow"), "error");
|
||||
} finally {
|
||||
setApplying(false);
|
||||
}
|
||||
@@ -115,7 +120,7 @@ export function WorkflowSelector({
|
||||
disabled={disabled || loading || applying}
|
||||
onChange={(e) => void handleChange(e.target.value)}
|
||||
>
|
||||
<option value="">None</option>
|
||||
<option value="">{t("workflowSelector.none", "None")}</option>
|
||||
{workflows.map((w) => (
|
||||
<option key={w.id} value={w.id}>
|
||||
{w.name}
|
||||
@@ -125,7 +130,7 @@ export function WorkflowSelector({
|
||||
</div>
|
||||
{onManage && (
|
||||
<button type="button" className="workflow-selector-manage" onClick={onManage}>
|
||||
Manage…
|
||||
{t("workflowSelector.manage", "Manage…")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -140,6 +145,7 @@ interface ProjectDefaultWorkflowFieldProps {
|
||||
|
||||
/** Self-contained project-default workflow picker for the settings modal. */
|
||||
export function ProjectDefaultWorkflowField({ projectId, addToast, onManage }: ProjectDefaultWorkflowFieldProps) {
|
||||
const { t } = useTranslation("app");
|
||||
const [value, setValue] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -162,9 +168,9 @@ export function ProjectDefaultWorkflowField({ projectId, addToast, onManage }: P
|
||||
async (workflowId: string | null) => {
|
||||
const res = await setProjectDefaultWorkflow(workflowId, projectId);
|
||||
setValue(res.workflowId);
|
||||
addToast?.(workflowId ? "Default workflow set" : "Default workflow cleared", "success");
|
||||
addToast?.(workflowId ? t("workflowSelector.defaultSet", "Default workflow set") : t("workflowSelector.defaultCleared", "Default workflow cleared"), "success");
|
||||
},
|
||||
[projectId, addToast],
|
||||
[projectId, addToast, t],
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -173,7 +179,7 @@ export function ProjectDefaultWorkflowField({ projectId, addToast, onManage }: P
|
||||
onChange={handleChange}
|
||||
projectId={projectId}
|
||||
addToast={addToast}
|
||||
label="Default workflow for new tasks"
|
||||
label={t("workflowSelector.defaultWorkflowLabel", "Default workflow for new tasks")}
|
||||
onManage={onManage}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1181,7 +1181,11 @@
|
||||
"workflowMismatch": "Drag can't move a card between workflows. Use the workflow switcher instead."
|
||||
},
|
||||
"todo": "To Do",
|
||||
"triage": "Triage"
|
||||
"triage": "Triage",
|
||||
"workflow": {
|
||||
"edit": "Edit workflows",
|
||||
"new": "New workflow"
|
||||
}
|
||||
},
|
||||
"branchGroup": {
|
||||
"abandonGroup": "Abandon group",
|
||||
@@ -1810,7 +1814,8 @@
|
||||
"heading": "Comments",
|
||||
"placeholder": "Add a comment",
|
||||
"postingButton": "Posting…",
|
||||
"updatedSuccess": "Comment updated"
|
||||
"updatedSuccess": "Comment updated",
|
||||
"aiGuidance": "AI Guidance"
|
||||
},
|
||||
"commit": {
|
||||
"filesChanged_one": "Files Changed ({{count}})",
|
||||
@@ -4686,7 +4691,60 @@
|
||||
"rerunPreflight": "Re-run preflight",
|
||||
"revertToAi": "Revert to AI version",
|
||||
"titleLabel": "Title",
|
||||
"usingTemplate": "Using <code>.github/pull_request_template.md</code>"
|
||||
"usingTemplate": "Using <code>.github/pull_request_template.md</code>",
|
||||
"assignees": "Assignees",
|
||||
"dismissConflictResolutionError": "Dismiss conflict resolution error",
|
||||
"dismissPushBranchError": "Dismiss push branch error",
|
||||
"error": {
|
||||
"actionOpen": "Action: open",
|
||||
"actionRun": "Action: run",
|
||||
"docs": "docs"
|
||||
},
|
||||
"generatingBody": "Generating AI body…",
|
||||
"generatingTitle": "Generating AI title…",
|
||||
"labels": "Labels",
|
||||
"loadingOptions": "Loading PR options…",
|
||||
"loadingPreflight": "Loading pre-flight checks…",
|
||||
"noChangedFiles": "No changed files detected.",
|
||||
"noCommits": "No commits found.",
|
||||
"pushBranch": {
|
||||
"button": "Push branch to remote",
|
||||
"message": "Fusion will push this task's branch to origin so the PR can be created.",
|
||||
"title": "Push branch to remote"
|
||||
},
|
||||
"resolveConflicts": {
|
||||
"button": "Resolve conflicts with AI",
|
||||
"message": "Fusion will use AI to resolve conflicts on this branch and push it.",
|
||||
"title": "Resolve conflicts with AI"
|
||||
},
|
||||
"reviewers": "Reviewers",
|
||||
"view": {
|
||||
"agentDisagreed": "agent disagreed",
|
||||
"agentReplyFix": "Agent reply — fix {{sha}}",
|
||||
"approve": "Approve",
|
||||
"autoMerge": "Auto-merge",
|
||||
"close": "Close",
|
||||
"confirmMerge": "Confirm merge",
|
||||
"creating": "Creating PR…",
|
||||
"creationFailed": "PR creation failed",
|
||||
"loading": "Loading PR…",
|
||||
"merge": "Merge",
|
||||
"mergeableLabel": "Mergeable:",
|
||||
"mergeDisabledUntilVerified": "Merge is disabled until GitHub verifies this PR",
|
||||
"none": "none",
|
||||
"noReviewThreads": "No review threads.",
|
||||
"requestRetry": "Request retry",
|
||||
"resolveConflictsBeforeMerge": "Resolve conflicts on GitHub before merging",
|
||||
"resolveConflictsOnGithub": "Resolve conflicts on GitHub",
|
||||
"responseAlreadyInProgress": "A response run is already in progress",
|
||||
"responsePending_one": "Response run in progress — {{count}} threads pending",
|
||||
"responsePending_other": "Response run in progress — {{count}} threads pending",
|
||||
"retryCreation": "Retry PR creation",
|
||||
"reviewLabel": "Review:",
|
||||
"threadFixed": "fixed",
|
||||
"threadPending": "pending",
|
||||
"verifyingGithub": "Verifying with GitHub…"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"blockedDescription": "You can view the preview in a separate browser tab.",
|
||||
@@ -5553,7 +5611,8 @@
|
||||
"totalSize": "total size",
|
||||
"view": "View ",
|
||||
"whenEnabledProjectAndAgentMemoryFilesAre": "When enabled, project and agent memory files are backed up automatically on a schedule.",
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": "When enabled, the database is backed up automatically on a schedule"
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": "When enabled, the database is backed up automatically on a schedule",
|
||||
"createFailed": "Failed to create backup"
|
||||
},
|
||||
"clearToDefault": "Reset to default",
|
||||
"cliAgents": {
|
||||
@@ -5597,7 +5656,9 @@
|
||||
},
|
||||
"footer": {
|
||||
"help": "Help",
|
||||
"version": "Version {{version}}"
|
||||
"version": "Version {{version}}",
|
||||
"checkUpdates": "Check for updates",
|
||||
"helpDiscussions": "Help and discussions"
|
||||
},
|
||||
"general": {
|
||||
"25": "25",
|
||||
@@ -5746,7 +5807,10 @@
|
||||
"whenEnabledStartupRefreshesModelsThroughTheLocal": " When enabled, startup refreshes models through the local "
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord"
|
||||
"discord": "Discord",
|
||||
"joinDiscord": "Join our Discord",
|
||||
"star": "Star",
|
||||
"starFusion": "Star Fusion on GitHub"
|
||||
},
|
||||
"importExport": {
|
||||
"confirmImport": "Confirm Import",
|
||||
@@ -5756,7 +5820,35 @@
|
||||
"importing": "Importing…",
|
||||
"importTitle": "Import Settings",
|
||||
"loadingFile": "Loading…",
|
||||
"reviewPrompt": "Review the settings to be imported:"
|
||||
"reviewPrompt": "Review the settings to be imported:",
|
||||
"counts": {
|
||||
"global_one": "{{count}} global",
|
||||
"global_other": "{{count}} global",
|
||||
"project_one": "{{count}} project",
|
||||
"project_other": "{{count}} project",
|
||||
"workflowSettings_one": "{{count}} workflow setting value",
|
||||
"workflowSettings_other": "{{count}} workflow setting value"
|
||||
},
|
||||
"exported": "Settings exported ({{scope}} scope)",
|
||||
"exportFailed": "Failed to export settings",
|
||||
"globalSettings": "Global Settings:",
|
||||
"imported": "Imported {{counts}} setting(s)",
|
||||
"importFailed": "Import failed",
|
||||
"importFailedDetailed": "Failed to import settings",
|
||||
"importScope": "Import Scope:",
|
||||
"importTitleAttr": "Import settings from JSON file",
|
||||
"invalidJson": "Invalid JSON file: {{error}}",
|
||||
"mergeExisting": "Merge with existing settings (recommended)",
|
||||
"projectSettings": "Project Settings:",
|
||||
"replaceWarning": "If unchecked, existing settings will be replaced with imported values.",
|
||||
"scopeBoth": "Both global and project settings",
|
||||
"scopeGlobal": "Global settings only",
|
||||
"scopeLabel": {
|
||||
"all": "all",
|
||||
"global": "global",
|
||||
"project": "project"
|
||||
},
|
||||
"scopeProject": "Project settings only"
|
||||
},
|
||||
"jsonPlaceholder": "Enter JSON value...",
|
||||
"keepLocal": "Keep Local",
|
||||
@@ -5806,7 +5898,10 @@
|
||||
"searchMemoryWithQmd": "Search memory with qmd",
|
||||
"testing": "Testing…",
|
||||
"testRetrieval": "Test Retrieval",
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md."
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "Turns daily notes into DREAMS.md and promotes reusable lessons into MEMORY.md.",
|
||||
"qmdInstalled": "qmd installed successfully",
|
||||
"qmdInstallFailed": "Failed to install qmd",
|
||||
"qmdInstallUnavailable": "qmd install finished, but qmd is still unavailable"
|
||||
},
|
||||
"merge": {
|
||||
"abort": "Abort",
|
||||
@@ -6155,7 +6250,8 @@
|
||||
"uRLAndQRGenerationUseTheSelectedToken": " URL and QR generation use the selected token type. ",
|
||||
"uRLNoHostnameOrPortConfigurationNeeded": " URL — no hostname or port configuration needed.",
|
||||
"useExisting": "Use Existing",
|
||||
"usingQuickTunnel": "Using Quick Tunnel — automatically creates a random trycloudflare.com URL, no account needed."
|
||||
"usingQuickTunnel": "Using Quick Tunnel — automatically creates a random trycloudflare.com URL, no account needed.",
|
||||
"installationFailed": "Installation failed"
|
||||
},
|
||||
"researchGlobal": {
|
||||
"advancedExternalSearchProviders": "Advanced — external search providers",
|
||||
@@ -6275,7 +6371,9 @@
|
||||
"timeoutInMinutesForDetectingStuckTasksWhen": "Timeout in minutes for detecting stuck tasks. When a task's agent session shows no activity for longer than this duration, the task is terminated and retried. Leave empty to disable. Suggested: 10.",
|
||||
"whenEnabledTasksThatModifyTheSameFiles": "When enabled, tasks that modify the same files are queued serially to avoid merge conflicts",
|
||||
"whenEnabledTasksWithStalePlansPROMPTMd": "When enabled, tasks with stale plans (PROMPT.md older than the threshold) are automatically sent back to planning for replanning",
|
||||
"whenTheStuckDetectorKillsAndReQueues": "When the stuck detector kills and re-queues a task, keep completed step statuses so the agent can resume from where it left off. Disable to reset every step to pending on each stuck retry. Default: enabled."
|
||||
"whenTheStuckDetectorKillsAndReQueues": "When the stuck detector kills and re-queues a task, keep completed step statuses so the agent can resume from where it left off. Disable to reset every step to pending on each stuck retry. Default: enabled.",
|
||||
"browseWorkspacePath": "Browse workspace path",
|
||||
"overlapPickerNote": "Choose a file to ignore directly, or navigate into a folder and select the current directory."
|
||||
},
|
||||
"scope": {
|
||||
"globalBanner": "These settings are shared across all your Fusion projects.",
|
||||
@@ -6335,7 +6433,12 @@
|
||||
"worktrunk": " worktrunk ",
|
||||
"worktrunkBinaryPath": "Worktrunk binary path",
|
||||
"worktrunkFailureBehavior": "Worktrunk failure behavior",
|
||||
"worktrunkIntegration": "Worktrunk integration"
|
||||
"worktrunkIntegration": "Worktrunk integration",
|
||||
"worktreesPickerNote": "Navigate to the folder where Fusion should create task worktrees, then select the current directory."
|
||||
},
|
||||
"fileBrowser": {
|
||||
"currentDirectory": "Current directory:",
|
||||
"projectRoot": "(project root)"
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
@@ -6655,7 +6758,10 @@
|
||||
"withoutGitHub1": "Create tasks manually",
|
||||
"withoutGitHub2": "Describe work for AI agents",
|
||||
"withoutGitHub3": "Track progress on the board",
|
||||
"withoutGitHubHeading": "Without GitHub (available now):"
|
||||
"withoutGitHubHeading": "Without GitHub (available now):",
|
||||
"brandLogo": "Fusion logo",
|
||||
"brandName": "Fusion",
|
||||
"setupCompleteTitle": "Setup Complete!"
|
||||
},
|
||||
"shell": {
|
||||
"activePill": "Active",
|
||||
@@ -7201,7 +7307,8 @@
|
||||
"mergingFixes": "Merging fixes…",
|
||||
"mergingPr": "Merging PR…",
|
||||
"startPrReview": "Start PR Review",
|
||||
"statusRefreshed": "PR status refreshed"
|
||||
"statusRefreshed": "PR status refreshed",
|
||||
"label": "PR"
|
||||
},
|
||||
"priority": {
|
||||
"ariaLabel": "Task priority",
|
||||
@@ -7216,7 +7323,8 @@
|
||||
},
|
||||
"provenance": {
|
||||
"createdBy": "Created by",
|
||||
"createdVia": "Created via"
|
||||
"createdVia": "Created via",
|
||||
"parentTaskOf": "of"
|
||||
},
|
||||
"recoveryState": "Recovery state",
|
||||
"refine": {
|
||||
@@ -7955,7 +8063,43 @@
|
||||
"switchToMarkdown": "Switch to markdown",
|
||||
"switchToPlain": "Switch to plain text",
|
||||
"thinkingLevel": "Thinking level",
|
||||
"workflowName": "Workflow"
|
||||
"workflowName": "Workflow",
|
||||
"aggregateAdvisory": "Advisory",
|
||||
"aggregateAllPassed": "All passed",
|
||||
"aggregateInProgress": "In progress",
|
||||
"aggregateNoResults": "No results",
|
||||
"approveAndRun": "Approve & run",
|
||||
"approveCommandError": "Failed to approve command",
|
||||
"approving": "Approving…",
|
||||
"awaitingInputTitle": "Waiting for your input",
|
||||
"cliApprovalRejectHint": "To reject, keep the task paused and do not approve.",
|
||||
"cliApprovalTitle": "Approve CLI command?",
|
||||
"cliApprovalWarning": "This command will run in the task worktree. Approving trusts this exact command for future runs.",
|
||||
"customWorkflowFallback": "Custom workflow",
|
||||
"customWorkflowLabel": "Custom workflow",
|
||||
"defaultWorkflow": "Default",
|
||||
"executionAwaitingCliApproval": "Awaiting CLI approval",
|
||||
"executionAwaitingInput": "Awaiting input",
|
||||
"executionCompleted": "Completed",
|
||||
"executionNotStarted": "Not started",
|
||||
"executionPaused": "Paused",
|
||||
"executionPostMerge": "Post-merge steps running",
|
||||
"executionPreMerge": "Pre-merge steps running",
|
||||
"inputPlaceholder": "Type your reply…",
|
||||
"modelDefault": "Default",
|
||||
"postMerge": "Post-merge",
|
||||
"preMerge": "Pre-merge",
|
||||
"replyInComments": "Reply in the comments and unpause the task to continue.",
|
||||
"resumeTaskError": "Failed to resume task",
|
||||
"resuming": "Resuming…",
|
||||
"statusAdvisory": "Advisory failure",
|
||||
"statusFailed": "Failed",
|
||||
"statusPassed": "Passed",
|
||||
"statusRunning": "Running…",
|
||||
"statusSkipped": "Skipped",
|
||||
"submitAndResume": "Submit & resume",
|
||||
"submitting": "Submitting…",
|
||||
"waitingForOutput": "Waiting for agent output…"
|
||||
},
|
||||
"workflowColumns": {
|
||||
"add": "Add column",
|
||||
@@ -8007,7 +8151,30 @@
|
||||
},
|
||||
"collapsePrompt": "Collapse prompt editor",
|
||||
"editingPrompt": "Editing Prompt",
|
||||
"expandPrompt": "Expand prompt editor"
|
||||
"expandPrompt": "Expand prompt editor",
|
||||
"agent": "Agent",
|
||||
"agentsLoadFailed": "Failed to load agents",
|
||||
"autoApproveRequests": "Auto-approve requests",
|
||||
"autoApproveRequestsNote": "Runs without pausing for approval — e.g. a CLI command executes on its first run without waiting for your sign-off.",
|
||||
"cliCommandNote": "Runs an arbitrary command in the task worktree. The first time this exact command runs, the task pauses for your approval. The node prompt is passed via FUSION_NODE_PROMPT.",
|
||||
"cliMode": "CLI mode",
|
||||
"cliScript": "CLI / script",
|
||||
"command": "Command",
|
||||
"executor": "Executor",
|
||||
"maxRetries": "Max retries",
|
||||
"model": "Model",
|
||||
"modelsLoadFailed": "Failed to load models",
|
||||
"namedScript": "Named script",
|
||||
"namedScriptNote": "Named script from project settings. The node prompt is passed via FUSION_NODE_PROMPT.",
|
||||
"prompt": "Prompt",
|
||||
"scriptName": "Script name",
|
||||
"selectAgent": "— select agent —",
|
||||
"selectSkill": "— select skill —",
|
||||
"skill": "Skill",
|
||||
"skillsLoadFailed": "Failed to load skills",
|
||||
"skipFirstRunApproval": "Skip first-run approval (runs without pausing)",
|
||||
"waitForUserInput": "Wait for user input",
|
||||
"waitForUserInputNote": "This node pauses the task until you reply in the task's comments and unpause. The Prompt field above is shown to the user as the question."
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "Add field",
|
||||
@@ -8141,7 +8308,11 @@
|
||||
"templatesPluginSteps": "Plugin steps",
|
||||
"templatesSection": "Templates",
|
||||
"timeoutMs": "{{timeout}}ms",
|
||||
"trivialGraphHint": "This workflow only runs start → end. Add steps from the palette above to build it out."
|
||||
"trivialGraphHint": "This workflow only runs start → end. Add steps from the palette above to build it out.",
|
||||
"conditionFailure": "failure",
|
||||
"conditionSuccess": "success",
|
||||
"nodeInspector": "Node",
|
||||
"readOnlyDuplicateToEdit": "Read-only built-in — duplicate the workflow to edit nodes."
|
||||
},
|
||||
"workflows": {
|
||||
"aiEdit": "Design with AI",
|
||||
@@ -8204,13 +8375,27 @@
|
||||
"templateNodeCount_other": "{{count}} nodes",
|
||||
"templatePickerLabel": "Start from",
|
||||
"templateSectionBuiltin": "Built-in workflows",
|
||||
"templateSectionYours": "Your workflows"
|
||||
"templateSectionYours": "Your workflows",
|
||||
"closeEditor": "Close workflow editor",
|
||||
"duplicatedEditable": "Duplicated to \"{{name}}\" — editable",
|
||||
"duplicateFailed": "Failed to duplicate workflow",
|
||||
"loadFailed": "Failed to load workflows",
|
||||
"loading": "Loading…",
|
||||
"noneYet": "No workflows yet.",
|
||||
"title": "Workflows"
|
||||
},
|
||||
"workflowSelector": {
|
||||
"switchActiveMessage": "This task has an active session. Switching workflows aborts it and re-homes the card into the new workflow's entry column. Continue?",
|
||||
"switchActiveTitle": "Switch workflow?",
|
||||
"switchCancel": "Cancel",
|
||||
"switchConfirm": "Switch and abort"
|
||||
"switchConfirm": "Switch and abort",
|
||||
"applyFailed": "Failed to apply workflow",
|
||||
"defaultCleared": "Default workflow cleared",
|
||||
"defaultSet": "Default workflow set",
|
||||
"defaultWorkflowLabel": "Default workflow for new tasks",
|
||||
"loadFailed": "Failed to load workflows",
|
||||
"manage": "Manage…",
|
||||
"none": "None"
|
||||
},
|
||||
"workflowSettings": {
|
||||
"add": "Add setting",
|
||||
@@ -8288,5 +8473,50 @@
|
||||
"installRequestTitle": "Worktrunk install request",
|
||||
"sha256": "SHA-256",
|
||||
"version": "Version"
|
||||
},
|
||||
"taskChat": {
|
||||
"activePlaceholder": "Steer the currently executing agent",
|
||||
"activeSessionHint": "Message the active agent session. Guidance is delivered to the running session in real time.",
|
||||
"agentMessages": "{{label}} messages",
|
||||
"arguments": "Arguments",
|
||||
"collapseChat": "Collapse chat",
|
||||
"donePlaceholder": "Start a refinement task for this completed task",
|
||||
"doneSessionHint": "Send a message to start a refinement task for this completed task.",
|
||||
"emptyAgentOutput": "No agent output yet. Live messages from Planner, Executor, Reviewer, and Merger agents will appear here.",
|
||||
"entryCount_one": "{{count}} entry",
|
||||
"entryCount_other": "{{count}} entries",
|
||||
"error": "Error",
|
||||
"errorCount_one": "{{count}} error",
|
||||
"errorCount_other": "{{count}} errors",
|
||||
"expandChat": "Expand chat to full modal",
|
||||
"idleSessionHint": "No agent is working on this task right now. Your message is saved as guidance and will reach an agent the next time this task runs.",
|
||||
"jumpToLatestMessage": "Jump to latest message",
|
||||
"latest": "Latest",
|
||||
"loadingAgentOutput": "Loading agent output…",
|
||||
"loadingEarlierMessages": "Loading earlier messages…",
|
||||
"loadPreviousMessages": "Load previous messages",
|
||||
"message": "Message",
|
||||
"messageActiveAgentSession": "Message active agent session",
|
||||
"moreTools_one": ", +{{count}} more",
|
||||
"moreTools_other": ", +{{count}} more",
|
||||
"result": "Result",
|
||||
"roles": {
|
||||
"agent": "Agent",
|
||||
"executor": "Executor",
|
||||
"merger": "Merger",
|
||||
"planner": "Planner",
|
||||
"reviewer": "Reviewer"
|
||||
},
|
||||
"sending": "Sending",
|
||||
"thinking": "Thinking",
|
||||
"toolCall": "Tool call",
|
||||
"toolCallCount_one": "{{count}} tool call",
|
||||
"toolCallCount_other": "{{count}} tool calls",
|
||||
"toolCallTo": "Tool call → {{label}}",
|
||||
"toolError": "Tool error",
|
||||
"toolNames": "Tool names",
|
||||
"toolResult": "Tool result",
|
||||
"you": "You",
|
||||
"youMessage": "You message"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"actions": {
|
||||
"send": "Send"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "↓ Declining",
|
||||
@@ -78,6 +81,9 @@
|
||||
"offline": "Offline",
|
||||
"online": "Online"
|
||||
},
|
||||
"labels": {
|
||||
"name": "Name"
|
||||
},
|
||||
"merge": {
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
@@ -214,28 +220,6 @@
|
||||
"refreshSourceInitialLoad": "Initial load",
|
||||
"refreshSourceManual": "Manual"
|
||||
},
|
||||
"workflow": {
|
||||
"aggregateAllPassed": "All passed",
|
||||
"aggregateInProgress": "In progress",
|
||||
"aggregateNoResults": "No results",
|
||||
"customWorkflowFallback": "Custom workflow",
|
||||
"defaultWorkflow": "Default",
|
||||
"executionAwaitingCliApproval": "Awaiting CLI approval",
|
||||
"executionAwaitingInput": "Awaiting input",
|
||||
"executionCompleted": "Completed",
|
||||
"executionNotStarted": "Not started",
|
||||
"executionPaused": "Paused",
|
||||
"executionPostMerge": "Post-merge steps running",
|
||||
"executionPreMerge": "Pre-merge steps running",
|
||||
"postMerge": "Post-merge",
|
||||
"preMerge": "Pre-merge",
|
||||
"statusAdvisory": "Advisory failure",
|
||||
"statusFailed": "Failed",
|
||||
"statusPassed": "Passed",
|
||||
"statusRunning": "Running…",
|
||||
"statusSkipped": "Skipped",
|
||||
"waitingForOutput": "Waiting for agent output…"
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "Waits for user input",
|
||||
"summaryCodeDefault": "TypeScript",
|
||||
|
||||
@@ -1181,7 +1181,11 @@
|
||||
"workflowMismatch": ""
|
||||
},
|
||||
"todo": "Por hacer",
|
||||
"triage": "Triaje"
|
||||
"triage": "Triaje",
|
||||
"workflow": {
|
||||
"edit": "",
|
||||
"new": ""
|
||||
}
|
||||
},
|
||||
"branchGroup": {
|
||||
"abandonGroup": "",
|
||||
@@ -1810,7 +1814,8 @@
|
||||
"heading": "Comentarios",
|
||||
"placeholder": "Añadir un comentario",
|
||||
"postingButton": "Publicando…",
|
||||
"updatedSuccess": "Comentario actualizado"
|
||||
"updatedSuccess": "Comentario actualizado",
|
||||
"aiGuidance": ""
|
||||
},
|
||||
"commit": {
|
||||
"filesChanged_one": "",
|
||||
@@ -4686,7 +4691,60 @@
|
||||
"rerunPreflight": "Volver a ejecutar la comprobación previa",
|
||||
"revertToAi": "Revertir a la versión de IA",
|
||||
"titleLabel": "Título",
|
||||
"usingTemplate": "Usando <code>.github/pull_request_template.md</code>"
|
||||
"usingTemplate": "Usando <code>.github/pull_request_template.md</code>",
|
||||
"assignees": "",
|
||||
"dismissConflictResolutionError": "",
|
||||
"dismissPushBranchError": "",
|
||||
"error": {
|
||||
"actionOpen": "",
|
||||
"actionRun": "",
|
||||
"docs": ""
|
||||
},
|
||||
"generatingBody": "",
|
||||
"generatingTitle": "",
|
||||
"labels": "",
|
||||
"loadingOptions": "",
|
||||
"loadingPreflight": "",
|
||||
"noChangedFiles": "",
|
||||
"noCommits": "",
|
||||
"pushBranch": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"resolveConflicts": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"reviewers": "",
|
||||
"view": {
|
||||
"agentDisagreed": "",
|
||||
"agentReplyFix": "",
|
||||
"approve": "",
|
||||
"autoMerge": "",
|
||||
"close": "",
|
||||
"confirmMerge": "",
|
||||
"creating": "",
|
||||
"creationFailed": "",
|
||||
"loading": "",
|
||||
"merge": "",
|
||||
"mergeableLabel": "",
|
||||
"mergeDisabledUntilVerified": "",
|
||||
"none": "",
|
||||
"noReviewThreads": "",
|
||||
"requestRetry": "",
|
||||
"resolveConflictsBeforeMerge": "",
|
||||
"resolveConflictsOnGithub": "",
|
||||
"responseAlreadyInProgress": "",
|
||||
"responsePending_one": "",
|
||||
"responsePending_other": "",
|
||||
"retryCreation": "",
|
||||
"reviewLabel": "",
|
||||
"threadFixed": "",
|
||||
"threadPending": "",
|
||||
"verifyingGithub": ""
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"blockedDescription": "Puede ver la vista previa en una pestaña del navegador separada.",
|
||||
@@ -5553,7 +5611,8 @@
|
||||
"totalSize": "",
|
||||
"view": "",
|
||||
"whenEnabledProjectAndAgentMemoryFilesAre": "",
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": ""
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": "",
|
||||
"createFailed": ""
|
||||
},
|
||||
"clearToDefault": "",
|
||||
"cliAgents": {
|
||||
@@ -5597,7 +5656,9 @@
|
||||
},
|
||||
"footer": {
|
||||
"help": "Ayuda",
|
||||
"version": "Versión {{version}}"
|
||||
"version": "Versión {{version}}",
|
||||
"checkUpdates": "",
|
||||
"helpDiscussions": ""
|
||||
},
|
||||
"general": {
|
||||
"25": "",
|
||||
@@ -5746,7 +5807,10 @@
|
||||
"whenEnabledStartupRefreshesModelsThroughTheLocal": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord"
|
||||
"discord": "Discord",
|
||||
"joinDiscord": "",
|
||||
"star": "",
|
||||
"starFusion": ""
|
||||
},
|
||||
"importExport": {
|
||||
"confirmImport": "Confirmar importación",
|
||||
@@ -5756,7 +5820,35 @@
|
||||
"importing": "Importando…",
|
||||
"importTitle": "Importar configuración",
|
||||
"loadingFile": "Cargando…",
|
||||
"reviewPrompt": "Revisa la configuración a importar:"
|
||||
"reviewPrompt": "Revisa la configuración a importar:",
|
||||
"counts": {
|
||||
"global_one": "",
|
||||
"global_other": "",
|
||||
"project_one": "",
|
||||
"project_other": "",
|
||||
"workflowSettings_one": "",
|
||||
"workflowSettings_other": ""
|
||||
},
|
||||
"exported": "",
|
||||
"exportFailed": "",
|
||||
"globalSettings": "",
|
||||
"imported": "",
|
||||
"importFailed": "",
|
||||
"importFailedDetailed": "",
|
||||
"importScope": "",
|
||||
"importTitleAttr": "",
|
||||
"invalidJson": "",
|
||||
"mergeExisting": "",
|
||||
"projectSettings": "",
|
||||
"replaceWarning": "",
|
||||
"scopeBoth": "",
|
||||
"scopeGlobal": "",
|
||||
"scopeLabel": {
|
||||
"all": "",
|
||||
"global": "",
|
||||
"project": ""
|
||||
},
|
||||
"scopeProject": ""
|
||||
},
|
||||
"jsonPlaceholder": "Ingresa un valor JSON...",
|
||||
"keepLocal": "Mantener local",
|
||||
@@ -5806,7 +5898,10 @@
|
||||
"searchMemoryWithQmd": "",
|
||||
"testing": "Probando…",
|
||||
"testRetrieval": "Probar recuperación",
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": ""
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6155,7 +6250,8 @@
|
||||
"uRLAndQRGenerationUseTheSelectedToken": "",
|
||||
"uRLNoHostnameOrPortConfigurationNeeded": "",
|
||||
"useExisting": "Usar existente",
|
||||
"usingQuickTunnel": ""
|
||||
"usingQuickTunnel": "",
|
||||
"installationFailed": ""
|
||||
},
|
||||
"researchGlobal": {
|
||||
"advancedExternalSearchProviders": "",
|
||||
@@ -6275,7 +6371,9 @@
|
||||
"timeoutInMinutesForDetectingStuckTasksWhen": "",
|
||||
"whenEnabledTasksThatModifyTheSameFiles": "",
|
||||
"whenEnabledTasksWithStalePlansPROMPTMd": "",
|
||||
"whenTheStuckDetectorKillsAndReQueues": ""
|
||||
"whenTheStuckDetectorKillsAndReQueues": "",
|
||||
"browseWorkspacePath": "",
|
||||
"overlapPickerNote": ""
|
||||
},
|
||||
"scope": {
|
||||
"globalBanner": "Estos ajustes se comparten entre todos tus proyectos de Fusion.",
|
||||
@@ -6335,7 +6433,12 @@
|
||||
"worktrunk": "",
|
||||
"worktrunkBinaryPath": "",
|
||||
"worktrunkFailureBehavior": "",
|
||||
"worktrunkIntegration": ""
|
||||
"worktrunkIntegration": "",
|
||||
"worktreesPickerNote": ""
|
||||
},
|
||||
"fileBrowser": {
|
||||
"currentDirectory": "",
|
||||
"projectRoot": ""
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
@@ -6655,7 +6758,10 @@
|
||||
"withoutGitHub1": "Crear tareas manualmente",
|
||||
"withoutGitHub2": "Describir trabajo para agentes de IA",
|
||||
"withoutGitHub3": "Seguir el progreso en el tablero",
|
||||
"withoutGitHubHeading": "Sin GitHub (disponible ahora):"
|
||||
"withoutGitHubHeading": "Sin GitHub (disponible ahora):",
|
||||
"brandLogo": "",
|
||||
"brandName": "",
|
||||
"setupCompleteTitle": ""
|
||||
},
|
||||
"shell": {
|
||||
"activePill": "Activo",
|
||||
@@ -7201,7 +7307,8 @@
|
||||
"mergingFixes": "Fusionando correcciones…",
|
||||
"mergingPr": "Fusionando PR…",
|
||||
"startPrReview": "Iniciar revisión del PR",
|
||||
"statusRefreshed": "Estado del PR actualizado"
|
||||
"statusRefreshed": "Estado del PR actualizado",
|
||||
"label": ""
|
||||
},
|
||||
"priority": {
|
||||
"ariaLabel": "Prioridad de la tarea",
|
||||
@@ -7216,7 +7323,8 @@
|
||||
},
|
||||
"provenance": {
|
||||
"createdBy": "Creado por",
|
||||
"createdVia": "Creado mediante"
|
||||
"createdVia": "Creado mediante",
|
||||
"parentTaskOf": ""
|
||||
},
|
||||
"recoveryState": "Estado de recuperación",
|
||||
"refine": {
|
||||
@@ -7955,7 +8063,43 @@
|
||||
"switchToMarkdown": "Cambiar a Markdown",
|
||||
"switchToPlain": "Cambiar a texto sin formato",
|
||||
"thinkingLevel": "",
|
||||
"workflowName": ""
|
||||
"workflowName": "",
|
||||
"aggregateAdvisory": "",
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"approveAndRun": "",
|
||||
"approveCommandError": "",
|
||||
"approving": "",
|
||||
"awaitingInputTitle": "",
|
||||
"cliApprovalRejectHint": "",
|
||||
"cliApprovalTitle": "",
|
||||
"cliApprovalWarning": "",
|
||||
"customWorkflowFallback": "",
|
||||
"customWorkflowLabel": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"inputPlaceholder": "",
|
||||
"modelDefault": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"replyInComments": "",
|
||||
"resumeTaskError": "",
|
||||
"resuming": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"submitAndResume": "",
|
||||
"submitting": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowColumns": {
|
||||
"add": "",
|
||||
@@ -8007,7 +8151,30 @@
|
||||
},
|
||||
"collapsePrompt": "",
|
||||
"editingPrompt": "",
|
||||
"expandPrompt": ""
|
||||
"expandPrompt": "",
|
||||
"agent": "",
|
||||
"agentsLoadFailed": "",
|
||||
"autoApproveRequests": "",
|
||||
"autoApproveRequestsNote": "",
|
||||
"cliCommandNote": "",
|
||||
"cliMode": "",
|
||||
"cliScript": "",
|
||||
"command": "",
|
||||
"executor": "",
|
||||
"maxRetries": "",
|
||||
"model": "",
|
||||
"modelsLoadFailed": "",
|
||||
"namedScript": "",
|
||||
"namedScriptNote": "",
|
||||
"prompt": "",
|
||||
"scriptName": "",
|
||||
"selectAgent": "",
|
||||
"selectSkill": "",
|
||||
"skill": "",
|
||||
"skillsLoadFailed": "",
|
||||
"skipFirstRunApproval": "",
|
||||
"waitForUserInput": "",
|
||||
"waitForUserInputNote": ""
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "Agregar campo",
|
||||
@@ -8141,7 +8308,11 @@
|
||||
"templatesPluginSteps": "Pasos de plugin",
|
||||
"templatesSection": "Plantillas",
|
||||
"timeoutMs": "",
|
||||
"trivialGraphHint": "Este flujo de trabajo solo ejecuta inicio → fin. Añade pasos desde la paleta superior para construirlo."
|
||||
"trivialGraphHint": "Este flujo de trabajo solo ejecuta inicio → fin. Añade pasos desde la paleta superior para construirlo.",
|
||||
"conditionFailure": "",
|
||||
"conditionSuccess": "",
|
||||
"nodeInspector": "",
|
||||
"readOnlyDuplicateToEdit": ""
|
||||
},
|
||||
"workflows": {
|
||||
"aiEdit": "Diseñar con IA",
|
||||
@@ -8204,13 +8375,27 @@
|
||||
"templateNodeCount_other": "{{count}} nodos",
|
||||
"templatePickerLabel": "Comenzar desde",
|
||||
"templateSectionBuiltin": "Flujos de trabajo integrados",
|
||||
"templateSectionYours": "Tus flujos de trabajo"
|
||||
"templateSectionYours": "Tus flujos de trabajo",
|
||||
"closeEditor": "",
|
||||
"duplicatedEditable": "",
|
||||
"duplicateFailed": "",
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"noneYet": "",
|
||||
"title": ""
|
||||
},
|
||||
"workflowSelector": {
|
||||
"switchActiveMessage": "",
|
||||
"switchActiveTitle": "",
|
||||
"switchCancel": "",
|
||||
"switchConfirm": ""
|
||||
"switchConfirm": "",
|
||||
"applyFailed": "",
|
||||
"defaultCleared": "",
|
||||
"defaultSet": "",
|
||||
"defaultWorkflowLabel": "",
|
||||
"loadFailed": "",
|
||||
"manage": "",
|
||||
"none": ""
|
||||
},
|
||||
"workflowSettings": {
|
||||
"add": "",
|
||||
@@ -8288,5 +8473,50 @@
|
||||
"installRequestTitle": "Solicitud de instalación de Worktrunk",
|
||||
"sha256": "SHA-256",
|
||||
"version": "Versión"
|
||||
},
|
||||
"taskChat": {
|
||||
"activePlaceholder": "",
|
||||
"activeSessionHint": "",
|
||||
"agentMessages": "",
|
||||
"arguments": "",
|
||||
"collapseChat": "",
|
||||
"donePlaceholder": "",
|
||||
"doneSessionHint": "",
|
||||
"emptyAgentOutput": "",
|
||||
"entryCount_one": "",
|
||||
"entryCount_other": "",
|
||||
"error": "",
|
||||
"errorCount_one": "",
|
||||
"errorCount_other": "",
|
||||
"expandChat": "",
|
||||
"idleSessionHint": "",
|
||||
"jumpToLatestMessage": "",
|
||||
"latest": "",
|
||||
"loadingAgentOutput": "",
|
||||
"loadingEarlierMessages": "",
|
||||
"loadPreviousMessages": "",
|
||||
"message": "",
|
||||
"messageActiveAgentSession": "",
|
||||
"moreTools_one": "",
|
||||
"moreTools_other": "",
|
||||
"result": "",
|
||||
"roles": {
|
||||
"agent": "",
|
||||
"executor": "",
|
||||
"merger": "",
|
||||
"planner": "",
|
||||
"reviewer": ""
|
||||
},
|
||||
"sending": "",
|
||||
"thinking": "",
|
||||
"toolCall": "",
|
||||
"toolCallCount_one": "",
|
||||
"toolCallCount_other": "",
|
||||
"toolCallTo": "",
|
||||
"toolError": "",
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"actions": {
|
||||
"send": ""
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -78,6 +81,9 @@
|
||||
"offline": "",
|
||||
"online": ""
|
||||
},
|
||||
"labels": {
|
||||
"name": ""
|
||||
},
|
||||
"merge": {
|
||||
"unknown": ""
|
||||
},
|
||||
@@ -214,28 +220,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"workflow": {
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"customWorkflowFallback": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
|
||||
@@ -1181,7 +1181,11 @@
|
||||
"workflowMismatch": ""
|
||||
},
|
||||
"todo": "À faire",
|
||||
"triage": "Triage"
|
||||
"triage": "Triage",
|
||||
"workflow": {
|
||||
"edit": "",
|
||||
"new": ""
|
||||
}
|
||||
},
|
||||
"branchGroup": {
|
||||
"abandonGroup": "",
|
||||
@@ -1810,7 +1814,8 @@
|
||||
"heading": "Commentaires",
|
||||
"placeholder": "Ajouter un commentaire",
|
||||
"postingButton": "Publication en cours…",
|
||||
"updatedSuccess": "Commentaire mis à jour"
|
||||
"updatedSuccess": "Commentaire mis à jour",
|
||||
"aiGuidance": ""
|
||||
},
|
||||
"commit": {
|
||||
"filesChanged_one": "",
|
||||
@@ -4686,7 +4691,60 @@
|
||||
"rerunPreflight": "Relancer les vérifications de pré-vol",
|
||||
"revertToAi": "Revenir à la version IA",
|
||||
"titleLabel": "Titre",
|
||||
"usingTemplate": "Utilisation de <code>.github/pull_request_template.md</code>"
|
||||
"usingTemplate": "Utilisation de <code>.github/pull_request_template.md</code>",
|
||||
"assignees": "",
|
||||
"dismissConflictResolutionError": "",
|
||||
"dismissPushBranchError": "",
|
||||
"error": {
|
||||
"actionOpen": "",
|
||||
"actionRun": "",
|
||||
"docs": ""
|
||||
},
|
||||
"generatingBody": "",
|
||||
"generatingTitle": "",
|
||||
"labels": "",
|
||||
"loadingOptions": "",
|
||||
"loadingPreflight": "",
|
||||
"noChangedFiles": "",
|
||||
"noCommits": "",
|
||||
"pushBranch": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"resolveConflicts": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"reviewers": "",
|
||||
"view": {
|
||||
"agentDisagreed": "",
|
||||
"agentReplyFix": "",
|
||||
"approve": "",
|
||||
"autoMerge": "",
|
||||
"close": "",
|
||||
"confirmMerge": "",
|
||||
"creating": "",
|
||||
"creationFailed": "",
|
||||
"loading": "",
|
||||
"merge": "",
|
||||
"mergeableLabel": "",
|
||||
"mergeDisabledUntilVerified": "",
|
||||
"none": "",
|
||||
"noReviewThreads": "",
|
||||
"requestRetry": "",
|
||||
"resolveConflictsBeforeMerge": "",
|
||||
"resolveConflictsOnGithub": "",
|
||||
"responseAlreadyInProgress": "",
|
||||
"responsePending_one": "",
|
||||
"responsePending_other": "",
|
||||
"retryCreation": "",
|
||||
"reviewLabel": "",
|
||||
"threadFixed": "",
|
||||
"threadPending": "",
|
||||
"verifyingGithub": ""
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"blockedDescription": "Vous pouvez afficher l'aperçu dans un onglet du navigateur séparé.",
|
||||
@@ -5553,7 +5611,8 @@
|
||||
"totalSize": "",
|
||||
"view": "",
|
||||
"whenEnabledProjectAndAgentMemoryFilesAre": "",
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": ""
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": "",
|
||||
"createFailed": ""
|
||||
},
|
||||
"clearToDefault": "",
|
||||
"cliAgents": {
|
||||
@@ -5597,7 +5656,9 @@
|
||||
},
|
||||
"footer": {
|
||||
"help": "Aide",
|
||||
"version": "Version {{version}}"
|
||||
"version": "Version {{version}}",
|
||||
"checkUpdates": "",
|
||||
"helpDiscussions": ""
|
||||
},
|
||||
"general": {
|
||||
"25": "",
|
||||
@@ -5746,7 +5807,10 @@
|
||||
"whenEnabledStartupRefreshesModelsThroughTheLocal": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord"
|
||||
"discord": "Discord",
|
||||
"joinDiscord": "",
|
||||
"star": "",
|
||||
"starFusion": ""
|
||||
},
|
||||
"importExport": {
|
||||
"confirmImport": "Confirmer l'importation",
|
||||
@@ -5756,7 +5820,35 @@
|
||||
"importing": "Importation…",
|
||||
"importTitle": "Importer les paramètres",
|
||||
"loadingFile": "Chargement…",
|
||||
"reviewPrompt": "Vérifiez les paramètres à importer :"
|
||||
"reviewPrompt": "Vérifiez les paramètres à importer :",
|
||||
"counts": {
|
||||
"global_one": "",
|
||||
"global_other": "",
|
||||
"project_one": "",
|
||||
"project_other": "",
|
||||
"workflowSettings_one": "",
|
||||
"workflowSettings_other": ""
|
||||
},
|
||||
"exported": "",
|
||||
"exportFailed": "",
|
||||
"globalSettings": "",
|
||||
"imported": "",
|
||||
"importFailed": "",
|
||||
"importFailedDetailed": "",
|
||||
"importScope": "",
|
||||
"importTitleAttr": "",
|
||||
"invalidJson": "",
|
||||
"mergeExisting": "",
|
||||
"projectSettings": "",
|
||||
"replaceWarning": "",
|
||||
"scopeBoth": "",
|
||||
"scopeGlobal": "",
|
||||
"scopeLabel": {
|
||||
"all": "",
|
||||
"global": "",
|
||||
"project": ""
|
||||
},
|
||||
"scopeProject": ""
|
||||
},
|
||||
"jsonPlaceholder": "Entrez une valeur JSON...",
|
||||
"keepLocal": "Garder la version locale",
|
||||
@@ -5806,7 +5898,10 @@
|
||||
"searchMemoryWithQmd": "",
|
||||
"testing": "Test en cours…",
|
||||
"testRetrieval": "Tester la récupération",
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": ""
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6155,7 +6250,8 @@
|
||||
"uRLAndQRGenerationUseTheSelectedToken": "",
|
||||
"uRLNoHostnameOrPortConfigurationNeeded": "",
|
||||
"useExisting": "Utiliser l'existant",
|
||||
"usingQuickTunnel": ""
|
||||
"usingQuickTunnel": "",
|
||||
"installationFailed": ""
|
||||
},
|
||||
"researchGlobal": {
|
||||
"advancedExternalSearchProviders": "",
|
||||
@@ -6275,7 +6371,9 @@
|
||||
"timeoutInMinutesForDetectingStuckTasksWhen": "",
|
||||
"whenEnabledTasksThatModifyTheSameFiles": "",
|
||||
"whenEnabledTasksWithStalePlansPROMPTMd": "",
|
||||
"whenTheStuckDetectorKillsAndReQueues": ""
|
||||
"whenTheStuckDetectorKillsAndReQueues": "",
|
||||
"browseWorkspacePath": "",
|
||||
"overlapPickerNote": ""
|
||||
},
|
||||
"scope": {
|
||||
"globalBanner": "Ces paramètres sont partagés entre tous vos projets Fusion.",
|
||||
@@ -6335,7 +6433,12 @@
|
||||
"worktrunk": "",
|
||||
"worktrunkBinaryPath": "",
|
||||
"worktrunkFailureBehavior": "",
|
||||
"worktrunkIntegration": ""
|
||||
"worktrunkIntegration": "",
|
||||
"worktreesPickerNote": ""
|
||||
},
|
||||
"fileBrowser": {
|
||||
"currentDirectory": "",
|
||||
"projectRoot": ""
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
@@ -6655,7 +6758,10 @@
|
||||
"withoutGitHub1": "Créer des tâches manuellement",
|
||||
"withoutGitHub2": "Décrire le travail pour les agents IA",
|
||||
"withoutGitHub3": "Suivre la progression sur le tableau",
|
||||
"withoutGitHubHeading": "Sans GitHub (disponible maintenant) :"
|
||||
"withoutGitHubHeading": "Sans GitHub (disponible maintenant) :",
|
||||
"brandLogo": "",
|
||||
"brandName": "",
|
||||
"setupCompleteTitle": ""
|
||||
},
|
||||
"shell": {
|
||||
"activePill": "Actif",
|
||||
@@ -7201,7 +7307,8 @@
|
||||
"mergingFixes": "Fusion des corrections…",
|
||||
"mergingPr": "Fusion du PR…",
|
||||
"startPrReview": "Démarrer la révision du PR",
|
||||
"statusRefreshed": "Statut du PR actualisé"
|
||||
"statusRefreshed": "Statut du PR actualisé",
|
||||
"label": ""
|
||||
},
|
||||
"priority": {
|
||||
"ariaLabel": "Priorité de la tâche",
|
||||
@@ -7216,7 +7323,8 @@
|
||||
},
|
||||
"provenance": {
|
||||
"createdBy": "Créé par",
|
||||
"createdVia": "Créé via"
|
||||
"createdVia": "Créé via",
|
||||
"parentTaskOf": ""
|
||||
},
|
||||
"recoveryState": "État de récupération",
|
||||
"refine": {
|
||||
@@ -7955,7 +8063,43 @@
|
||||
"switchToMarkdown": "Basculer vers Markdown",
|
||||
"switchToPlain": "Basculer vers texte brut",
|
||||
"thinkingLevel": "",
|
||||
"workflowName": ""
|
||||
"workflowName": "",
|
||||
"aggregateAdvisory": "",
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"approveAndRun": "",
|
||||
"approveCommandError": "",
|
||||
"approving": "",
|
||||
"awaitingInputTitle": "",
|
||||
"cliApprovalRejectHint": "",
|
||||
"cliApprovalTitle": "",
|
||||
"cliApprovalWarning": "",
|
||||
"customWorkflowFallback": "",
|
||||
"customWorkflowLabel": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"inputPlaceholder": "",
|
||||
"modelDefault": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"replyInComments": "",
|
||||
"resumeTaskError": "",
|
||||
"resuming": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"submitAndResume": "",
|
||||
"submitting": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowColumns": {
|
||||
"add": "",
|
||||
@@ -8007,7 +8151,30 @@
|
||||
},
|
||||
"collapsePrompt": "",
|
||||
"editingPrompt": "",
|
||||
"expandPrompt": ""
|
||||
"expandPrompt": "",
|
||||
"agent": "",
|
||||
"agentsLoadFailed": "",
|
||||
"autoApproveRequests": "",
|
||||
"autoApproveRequestsNote": "",
|
||||
"cliCommandNote": "",
|
||||
"cliMode": "",
|
||||
"cliScript": "",
|
||||
"command": "",
|
||||
"executor": "",
|
||||
"maxRetries": "",
|
||||
"model": "",
|
||||
"modelsLoadFailed": "",
|
||||
"namedScript": "",
|
||||
"namedScriptNote": "",
|
||||
"prompt": "",
|
||||
"scriptName": "",
|
||||
"selectAgent": "",
|
||||
"selectSkill": "",
|
||||
"skill": "",
|
||||
"skillsLoadFailed": "",
|
||||
"skipFirstRunApproval": "",
|
||||
"waitForUserInput": "",
|
||||
"waitForUserInputNote": ""
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "Ajouter un champ",
|
||||
@@ -8141,7 +8308,11 @@
|
||||
"templatesPluginSteps": "Étapes de plugin",
|
||||
"templatesSection": "Modèles",
|
||||
"timeoutMs": "",
|
||||
"trivialGraphHint": "Ce workflow ne fait qu'exécuter début → fin. Ajoutez des étapes depuis la palette ci-dessus pour le développer."
|
||||
"trivialGraphHint": "Ce workflow ne fait qu'exécuter début → fin. Ajoutez des étapes depuis la palette ci-dessus pour le développer.",
|
||||
"conditionFailure": "",
|
||||
"conditionSuccess": "",
|
||||
"nodeInspector": "",
|
||||
"readOnlyDuplicateToEdit": ""
|
||||
},
|
||||
"workflows": {
|
||||
"aiEdit": "Concevoir avec l'IA",
|
||||
@@ -8204,13 +8375,27 @@
|
||||
"templateNodeCount_other": "{{count}} nœuds",
|
||||
"templatePickerLabel": "Partir de",
|
||||
"templateSectionBuiltin": "Workflows intégrés",
|
||||
"templateSectionYours": "Vos workflows"
|
||||
"templateSectionYours": "Vos workflows",
|
||||
"closeEditor": "",
|
||||
"duplicatedEditable": "",
|
||||
"duplicateFailed": "",
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"noneYet": "",
|
||||
"title": ""
|
||||
},
|
||||
"workflowSelector": {
|
||||
"switchActiveMessage": "",
|
||||
"switchActiveTitle": "",
|
||||
"switchCancel": "",
|
||||
"switchConfirm": ""
|
||||
"switchConfirm": "",
|
||||
"applyFailed": "",
|
||||
"defaultCleared": "",
|
||||
"defaultSet": "",
|
||||
"defaultWorkflowLabel": "",
|
||||
"loadFailed": "",
|
||||
"manage": "",
|
||||
"none": ""
|
||||
},
|
||||
"workflowSettings": {
|
||||
"add": "",
|
||||
@@ -8288,5 +8473,50 @@
|
||||
"installRequestTitle": "Demande d'installation de Worktrunk",
|
||||
"sha256": "SHA-256",
|
||||
"version": "Version"
|
||||
},
|
||||
"taskChat": {
|
||||
"activePlaceholder": "",
|
||||
"activeSessionHint": "",
|
||||
"agentMessages": "",
|
||||
"arguments": "",
|
||||
"collapseChat": "",
|
||||
"donePlaceholder": "",
|
||||
"doneSessionHint": "",
|
||||
"emptyAgentOutput": "",
|
||||
"entryCount_one": "",
|
||||
"entryCount_other": "",
|
||||
"error": "",
|
||||
"errorCount_one": "",
|
||||
"errorCount_other": "",
|
||||
"expandChat": "",
|
||||
"idleSessionHint": "",
|
||||
"jumpToLatestMessage": "",
|
||||
"latest": "",
|
||||
"loadingAgentOutput": "",
|
||||
"loadingEarlierMessages": "",
|
||||
"loadPreviousMessages": "",
|
||||
"message": "",
|
||||
"messageActiveAgentSession": "",
|
||||
"moreTools_one": "",
|
||||
"moreTools_other": "",
|
||||
"result": "",
|
||||
"roles": {
|
||||
"agent": "",
|
||||
"executor": "",
|
||||
"merger": "",
|
||||
"planner": "",
|
||||
"reviewer": ""
|
||||
},
|
||||
"sending": "",
|
||||
"thinking": "",
|
||||
"toolCall": "",
|
||||
"toolCallCount_one": "",
|
||||
"toolCallCount_other": "",
|
||||
"toolCallTo": "",
|
||||
"toolError": "",
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"actions": {
|
||||
"send": ""
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -78,6 +81,9 @@
|
||||
"offline": "",
|
||||
"online": ""
|
||||
},
|
||||
"labels": {
|
||||
"name": ""
|
||||
},
|
||||
"merge": {
|
||||
"unknown": ""
|
||||
},
|
||||
@@ -214,28 +220,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"workflow": {
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"customWorkflowFallback": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
|
||||
@@ -1181,7 +1181,11 @@
|
||||
"workflowMismatch": ""
|
||||
},
|
||||
"todo": "할 일",
|
||||
"triage": "트리아지"
|
||||
"triage": "트리아지",
|
||||
"workflow": {
|
||||
"edit": "",
|
||||
"new": ""
|
||||
}
|
||||
},
|
||||
"branchGroup": {
|
||||
"abandonGroup": "",
|
||||
@@ -1810,7 +1814,8 @@
|
||||
"heading": "댓글",
|
||||
"placeholder": "댓글 추가",
|
||||
"postingButton": "게시 중…",
|
||||
"updatedSuccess": "댓글이 업데이트되었습니다"
|
||||
"updatedSuccess": "댓글이 업데이트되었습니다",
|
||||
"aiGuidance": ""
|
||||
},
|
||||
"commit": {
|
||||
"filesChanged_one": "",
|
||||
@@ -4686,7 +4691,60 @@
|
||||
"rerunPreflight": "사전 확인 다시 실행",
|
||||
"revertToAi": "AI 버전으로 되돌리기",
|
||||
"titleLabel": "제목",
|
||||
"usingTemplate": "<code>.github/pull_request_template.md</code> 템플릿 사용 중"
|
||||
"usingTemplate": "<code>.github/pull_request_template.md</code> 템플릿 사용 중",
|
||||
"assignees": "",
|
||||
"dismissConflictResolutionError": "",
|
||||
"dismissPushBranchError": "",
|
||||
"error": {
|
||||
"actionOpen": "",
|
||||
"actionRun": "",
|
||||
"docs": ""
|
||||
},
|
||||
"generatingBody": "",
|
||||
"generatingTitle": "",
|
||||
"labels": "",
|
||||
"loadingOptions": "",
|
||||
"loadingPreflight": "",
|
||||
"noChangedFiles": "",
|
||||
"noCommits": "",
|
||||
"pushBranch": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"resolveConflicts": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"reviewers": "",
|
||||
"view": {
|
||||
"agentDisagreed": "",
|
||||
"agentReplyFix": "",
|
||||
"approve": "",
|
||||
"autoMerge": "",
|
||||
"close": "",
|
||||
"confirmMerge": "",
|
||||
"creating": "",
|
||||
"creationFailed": "",
|
||||
"loading": "",
|
||||
"merge": "",
|
||||
"mergeableLabel": "",
|
||||
"mergeDisabledUntilVerified": "",
|
||||
"none": "",
|
||||
"noReviewThreads": "",
|
||||
"requestRetry": "",
|
||||
"resolveConflictsBeforeMerge": "",
|
||||
"resolveConflictsOnGithub": "",
|
||||
"responseAlreadyInProgress": "",
|
||||
"responsePending_one": "",
|
||||
"responsePending_other": "",
|
||||
"retryCreation": "",
|
||||
"reviewLabel": "",
|
||||
"threadFixed": "",
|
||||
"threadPending": "",
|
||||
"verifyingGithub": ""
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"blockedDescription": "별도의 브라우저 탭에서 미리보기를 볼 수 있습니다.",
|
||||
@@ -5553,7 +5611,8 @@
|
||||
"totalSize": "",
|
||||
"view": "",
|
||||
"whenEnabledProjectAndAgentMemoryFilesAre": "",
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": ""
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": "",
|
||||
"createFailed": ""
|
||||
},
|
||||
"clearToDefault": "",
|
||||
"cliAgents": {
|
||||
@@ -5597,7 +5656,9 @@
|
||||
},
|
||||
"footer": {
|
||||
"help": "도움말",
|
||||
"version": "버전 {{version}}"
|
||||
"version": "버전 {{version}}",
|
||||
"checkUpdates": "",
|
||||
"helpDiscussions": ""
|
||||
},
|
||||
"general": {
|
||||
"25": "",
|
||||
@@ -5746,7 +5807,10 @@
|
||||
"whenEnabledStartupRefreshesModelsThroughTheLocal": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord"
|
||||
"discord": "Discord",
|
||||
"joinDiscord": "",
|
||||
"star": "",
|
||||
"starFusion": ""
|
||||
},
|
||||
"importExport": {
|
||||
"confirmImport": "가져오기 확인",
|
||||
@@ -5756,7 +5820,35 @@
|
||||
"importing": "가져오는 중…",
|
||||
"importTitle": "설정 가져오기",
|
||||
"loadingFile": "불러오는 중…",
|
||||
"reviewPrompt": "가져올 설정을 검토하세요:"
|
||||
"reviewPrompt": "가져올 설정을 검토하세요:",
|
||||
"counts": {
|
||||
"global_one": "",
|
||||
"global_other": "",
|
||||
"project_one": "",
|
||||
"project_other": "",
|
||||
"workflowSettings_one": "",
|
||||
"workflowSettings_other": ""
|
||||
},
|
||||
"exported": "",
|
||||
"exportFailed": "",
|
||||
"globalSettings": "",
|
||||
"imported": "",
|
||||
"importFailed": "",
|
||||
"importFailedDetailed": "",
|
||||
"importScope": "",
|
||||
"importTitleAttr": "",
|
||||
"invalidJson": "",
|
||||
"mergeExisting": "",
|
||||
"projectSettings": "",
|
||||
"replaceWarning": "",
|
||||
"scopeBoth": "",
|
||||
"scopeGlobal": "",
|
||||
"scopeLabel": {
|
||||
"all": "",
|
||||
"global": "",
|
||||
"project": ""
|
||||
},
|
||||
"scopeProject": ""
|
||||
},
|
||||
"jsonPlaceholder": "JSON 값을 입력하세요...",
|
||||
"keepLocal": "로컬 유지",
|
||||
@@ -5806,7 +5898,10 @@
|
||||
"searchMemoryWithQmd": "",
|
||||
"testing": "테스트 중…",
|
||||
"testRetrieval": "검색 테스트",
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": ""
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6155,7 +6250,8 @@
|
||||
"uRLAndQRGenerationUseTheSelectedToken": "",
|
||||
"uRLNoHostnameOrPortConfigurationNeeded": "",
|
||||
"useExisting": "기존 사용",
|
||||
"usingQuickTunnel": ""
|
||||
"usingQuickTunnel": "",
|
||||
"installationFailed": ""
|
||||
},
|
||||
"researchGlobal": {
|
||||
"advancedExternalSearchProviders": "",
|
||||
@@ -6275,7 +6371,9 @@
|
||||
"timeoutInMinutesForDetectingStuckTasksWhen": "",
|
||||
"whenEnabledTasksThatModifyTheSameFiles": "",
|
||||
"whenEnabledTasksWithStalePlansPROMPTMd": "",
|
||||
"whenTheStuckDetectorKillsAndReQueues": ""
|
||||
"whenTheStuckDetectorKillsAndReQueues": "",
|
||||
"browseWorkspacePath": "",
|
||||
"overlapPickerNote": ""
|
||||
},
|
||||
"scope": {
|
||||
"globalBanner": "이 설정은 모든 Fusion 프로젝트에서 공유됩니다.",
|
||||
@@ -6335,7 +6433,12 @@
|
||||
"worktrunk": "",
|
||||
"worktrunkBinaryPath": "",
|
||||
"worktrunkFailureBehavior": "",
|
||||
"worktrunkIntegration": ""
|
||||
"worktrunkIntegration": "",
|
||||
"worktreesPickerNote": ""
|
||||
},
|
||||
"fileBrowser": {
|
||||
"currentDirectory": "",
|
||||
"projectRoot": ""
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
@@ -6655,7 +6758,10 @@
|
||||
"withoutGitHub1": "작업 수동 생성",
|
||||
"withoutGitHub2": "AI 에이전트를 위한 작업 설명",
|
||||
"withoutGitHub3": "보드에서 진행 상황 추적",
|
||||
"withoutGitHubHeading": "GitHub 없이 (지금 사용 가능):"
|
||||
"withoutGitHubHeading": "GitHub 없이 (지금 사용 가능):",
|
||||
"brandLogo": "",
|
||||
"brandName": "",
|
||||
"setupCompleteTitle": ""
|
||||
},
|
||||
"shell": {
|
||||
"activePill": "활성",
|
||||
@@ -7201,7 +7307,8 @@
|
||||
"mergingFixes": "수정 사항 병합 중…",
|
||||
"mergingPr": "PR 병합 중…",
|
||||
"startPrReview": "PR 검토 시작",
|
||||
"statusRefreshed": "PR 상태가 새로 고침되었습니다"
|
||||
"statusRefreshed": "PR 상태가 새로 고침되었습니다",
|
||||
"label": ""
|
||||
},
|
||||
"priority": {
|
||||
"ariaLabel": "작업 우선순위",
|
||||
@@ -7216,7 +7323,8 @@
|
||||
},
|
||||
"provenance": {
|
||||
"createdBy": "작성자",
|
||||
"createdVia": "생성 경로"
|
||||
"createdVia": "생성 경로",
|
||||
"parentTaskOf": ""
|
||||
},
|
||||
"recoveryState": "복구 상태",
|
||||
"refine": {
|
||||
@@ -7955,7 +8063,43 @@
|
||||
"switchToMarkdown": "Markdown으로 전환",
|
||||
"switchToPlain": "일반 텍스트로 전환",
|
||||
"thinkingLevel": "",
|
||||
"workflowName": ""
|
||||
"workflowName": "",
|
||||
"aggregateAdvisory": "",
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"approveAndRun": "",
|
||||
"approveCommandError": "",
|
||||
"approving": "",
|
||||
"awaitingInputTitle": "",
|
||||
"cliApprovalRejectHint": "",
|
||||
"cliApprovalTitle": "",
|
||||
"cliApprovalWarning": "",
|
||||
"customWorkflowFallback": "",
|
||||
"customWorkflowLabel": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"inputPlaceholder": "",
|
||||
"modelDefault": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"replyInComments": "",
|
||||
"resumeTaskError": "",
|
||||
"resuming": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"submitAndResume": "",
|
||||
"submitting": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowColumns": {
|
||||
"add": "",
|
||||
@@ -8007,7 +8151,30 @@
|
||||
},
|
||||
"collapsePrompt": "",
|
||||
"editingPrompt": "",
|
||||
"expandPrompt": ""
|
||||
"expandPrompt": "",
|
||||
"agent": "",
|
||||
"agentsLoadFailed": "",
|
||||
"autoApproveRequests": "",
|
||||
"autoApproveRequestsNote": "",
|
||||
"cliCommandNote": "",
|
||||
"cliMode": "",
|
||||
"cliScript": "",
|
||||
"command": "",
|
||||
"executor": "",
|
||||
"maxRetries": "",
|
||||
"model": "",
|
||||
"modelsLoadFailed": "",
|
||||
"namedScript": "",
|
||||
"namedScriptNote": "",
|
||||
"prompt": "",
|
||||
"scriptName": "",
|
||||
"selectAgent": "",
|
||||
"selectSkill": "",
|
||||
"skill": "",
|
||||
"skillsLoadFailed": "",
|
||||
"skipFirstRunApproval": "",
|
||||
"waitForUserInput": "",
|
||||
"waitForUserInputNote": ""
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "필드 추가",
|
||||
@@ -8141,7 +8308,11 @@
|
||||
"templatesPluginSteps": "플러그인 단계",
|
||||
"templatesSection": "템플릿",
|
||||
"timeoutMs": "",
|
||||
"trivialGraphHint": "이 워크플로는 시작 → 끝만 실행합니다. 위 팔레트에서 단계를 추가하여 구성하세요."
|
||||
"trivialGraphHint": "이 워크플로는 시작 → 끝만 실행합니다. 위 팔레트에서 단계를 추가하여 구성하세요.",
|
||||
"conditionFailure": "",
|
||||
"conditionSuccess": "",
|
||||
"nodeInspector": "",
|
||||
"readOnlyDuplicateToEdit": ""
|
||||
},
|
||||
"workflows": {
|
||||
"aiEdit": "AI로 디자인",
|
||||
@@ -8204,13 +8375,27 @@
|
||||
"templateNodeCount_other": "노드 {{count}}개",
|
||||
"templatePickerLabel": "시작점",
|
||||
"templateSectionBuiltin": "기본 제공 워크플로",
|
||||
"templateSectionYours": "내 워크플로"
|
||||
"templateSectionYours": "내 워크플로",
|
||||
"closeEditor": "",
|
||||
"duplicatedEditable": "",
|
||||
"duplicateFailed": "",
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"noneYet": "",
|
||||
"title": ""
|
||||
},
|
||||
"workflowSelector": {
|
||||
"switchActiveMessage": "",
|
||||
"switchActiveTitle": "",
|
||||
"switchCancel": "",
|
||||
"switchConfirm": ""
|
||||
"switchConfirm": "",
|
||||
"applyFailed": "",
|
||||
"defaultCleared": "",
|
||||
"defaultSet": "",
|
||||
"defaultWorkflowLabel": "",
|
||||
"loadFailed": "",
|
||||
"manage": "",
|
||||
"none": ""
|
||||
},
|
||||
"workflowSettings": {
|
||||
"add": "",
|
||||
@@ -8288,5 +8473,50 @@
|
||||
"installRequestTitle": "Worktrunk 설치 요청",
|
||||
"sha256": "SHA-256",
|
||||
"version": "버전"
|
||||
},
|
||||
"taskChat": {
|
||||
"activePlaceholder": "",
|
||||
"activeSessionHint": "",
|
||||
"agentMessages": "",
|
||||
"arguments": "",
|
||||
"collapseChat": "",
|
||||
"donePlaceholder": "",
|
||||
"doneSessionHint": "",
|
||||
"emptyAgentOutput": "",
|
||||
"entryCount_one": "",
|
||||
"entryCount_other": "",
|
||||
"error": "",
|
||||
"errorCount_one": "",
|
||||
"errorCount_other": "",
|
||||
"expandChat": "",
|
||||
"idleSessionHint": "",
|
||||
"jumpToLatestMessage": "",
|
||||
"latest": "",
|
||||
"loadingAgentOutput": "",
|
||||
"loadingEarlierMessages": "",
|
||||
"loadPreviousMessages": "",
|
||||
"message": "",
|
||||
"messageActiveAgentSession": "",
|
||||
"moreTools_one": "",
|
||||
"moreTools_other": "",
|
||||
"result": "",
|
||||
"roles": {
|
||||
"agent": "",
|
||||
"executor": "",
|
||||
"merger": "",
|
||||
"planner": "",
|
||||
"reviewer": ""
|
||||
},
|
||||
"sending": "",
|
||||
"thinking": "",
|
||||
"toolCall": "",
|
||||
"toolCallCount_one": "",
|
||||
"toolCallCount_other": "",
|
||||
"toolCallTo": "",
|
||||
"toolError": "",
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"actions": {
|
||||
"send": ""
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -78,6 +81,9 @@
|
||||
"offline": "",
|
||||
"online": ""
|
||||
},
|
||||
"labels": {
|
||||
"name": ""
|
||||
},
|
||||
"merge": {
|
||||
"unknown": ""
|
||||
},
|
||||
@@ -214,28 +220,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"workflow": {
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"customWorkflowFallback": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
|
||||
@@ -1181,7 +1181,11 @@
|
||||
"workflowMismatch": ""
|
||||
},
|
||||
"todo": "待办",
|
||||
"triage": "分诊"
|
||||
"triage": "分诊",
|
||||
"workflow": {
|
||||
"edit": "",
|
||||
"new": ""
|
||||
}
|
||||
},
|
||||
"branchGroup": {
|
||||
"abandonGroup": "",
|
||||
@@ -1810,7 +1814,8 @@
|
||||
"heading": "评论",
|
||||
"placeholder": "添加评论",
|
||||
"postingButton": "发布中…",
|
||||
"updatedSuccess": "评论已更新"
|
||||
"updatedSuccess": "评论已更新",
|
||||
"aiGuidance": ""
|
||||
},
|
||||
"commit": {
|
||||
"filesChanged_one": "",
|
||||
@@ -4686,7 +4691,60 @@
|
||||
"rerunPreflight": "重新运行飞行前检查",
|
||||
"revertToAi": "恢复为 AI 版本",
|
||||
"titleLabel": "标题",
|
||||
"usingTemplate": "使用 <code>.github/pull_request_template.md</code>"
|
||||
"usingTemplate": "使用 <code>.github/pull_request_template.md</code>",
|
||||
"assignees": "",
|
||||
"dismissConflictResolutionError": "",
|
||||
"dismissPushBranchError": "",
|
||||
"error": {
|
||||
"actionOpen": "",
|
||||
"actionRun": "",
|
||||
"docs": ""
|
||||
},
|
||||
"generatingBody": "",
|
||||
"generatingTitle": "",
|
||||
"labels": "",
|
||||
"loadingOptions": "",
|
||||
"loadingPreflight": "",
|
||||
"noChangedFiles": "",
|
||||
"noCommits": "",
|
||||
"pushBranch": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"resolveConflicts": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"reviewers": "",
|
||||
"view": {
|
||||
"agentDisagreed": "",
|
||||
"agentReplyFix": "",
|
||||
"approve": "",
|
||||
"autoMerge": "",
|
||||
"close": "",
|
||||
"confirmMerge": "",
|
||||
"creating": "",
|
||||
"creationFailed": "",
|
||||
"loading": "",
|
||||
"merge": "",
|
||||
"mergeableLabel": "",
|
||||
"mergeDisabledUntilVerified": "",
|
||||
"none": "",
|
||||
"noReviewThreads": "",
|
||||
"requestRetry": "",
|
||||
"resolveConflictsBeforeMerge": "",
|
||||
"resolveConflictsOnGithub": "",
|
||||
"responseAlreadyInProgress": "",
|
||||
"responsePending_one": "",
|
||||
"responsePending_other": "",
|
||||
"retryCreation": "",
|
||||
"reviewLabel": "",
|
||||
"threadFixed": "",
|
||||
"threadPending": "",
|
||||
"verifyingGithub": ""
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"blockedDescription": "您可以在单独的浏览器标签页中查看预览。",
|
||||
@@ -5553,7 +5611,8 @@
|
||||
"totalSize": "",
|
||||
"view": "",
|
||||
"whenEnabledProjectAndAgentMemoryFilesAre": "",
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": ""
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": "",
|
||||
"createFailed": ""
|
||||
},
|
||||
"clearToDefault": "",
|
||||
"cliAgents": {
|
||||
@@ -5597,7 +5656,9 @@
|
||||
},
|
||||
"footer": {
|
||||
"help": "帮助",
|
||||
"version": "版本 {{version}}"
|
||||
"version": "版本 {{version}}",
|
||||
"checkUpdates": "",
|
||||
"helpDiscussions": ""
|
||||
},
|
||||
"general": {
|
||||
"25": "",
|
||||
@@ -5746,7 +5807,10 @@
|
||||
"whenEnabledStartupRefreshesModelsThroughTheLocal": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord"
|
||||
"discord": "Discord",
|
||||
"joinDiscord": "",
|
||||
"star": "",
|
||||
"starFusion": ""
|
||||
},
|
||||
"importExport": {
|
||||
"confirmImport": "确认导入",
|
||||
@@ -5756,7 +5820,35 @@
|
||||
"importing": "导入中…",
|
||||
"importTitle": "导入设置",
|
||||
"loadingFile": "加载中…",
|
||||
"reviewPrompt": "查看要导入的设置:"
|
||||
"reviewPrompt": "查看要导入的设置:",
|
||||
"counts": {
|
||||
"global_one": "",
|
||||
"global_other": "",
|
||||
"project_one": "",
|
||||
"project_other": "",
|
||||
"workflowSettings_one": "",
|
||||
"workflowSettings_other": ""
|
||||
},
|
||||
"exported": "",
|
||||
"exportFailed": "",
|
||||
"globalSettings": "",
|
||||
"imported": "",
|
||||
"importFailed": "",
|
||||
"importFailedDetailed": "",
|
||||
"importScope": "",
|
||||
"importTitleAttr": "",
|
||||
"invalidJson": "",
|
||||
"mergeExisting": "",
|
||||
"projectSettings": "",
|
||||
"replaceWarning": "",
|
||||
"scopeBoth": "",
|
||||
"scopeGlobal": "",
|
||||
"scopeLabel": {
|
||||
"all": "",
|
||||
"global": "",
|
||||
"project": ""
|
||||
},
|
||||
"scopeProject": ""
|
||||
},
|
||||
"jsonPlaceholder": "输入 JSON 值...",
|
||||
"keepLocal": "保留本地",
|
||||
@@ -5806,7 +5898,10 @@
|
||||
"searchMemoryWithQmd": "",
|
||||
"testing": "测试中…",
|
||||
"testRetrieval": "测试检索",
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": ""
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6155,7 +6250,8 @@
|
||||
"uRLAndQRGenerationUseTheSelectedToken": "",
|
||||
"uRLNoHostnameOrPortConfigurationNeeded": "",
|
||||
"useExisting": "使用现有",
|
||||
"usingQuickTunnel": ""
|
||||
"usingQuickTunnel": "",
|
||||
"installationFailed": ""
|
||||
},
|
||||
"researchGlobal": {
|
||||
"advancedExternalSearchProviders": "",
|
||||
@@ -6275,7 +6371,9 @@
|
||||
"timeoutInMinutesForDetectingStuckTasksWhen": "",
|
||||
"whenEnabledTasksThatModifyTheSameFiles": "",
|
||||
"whenEnabledTasksWithStalePlansPROMPTMd": "",
|
||||
"whenTheStuckDetectorKillsAndReQueues": ""
|
||||
"whenTheStuckDetectorKillsAndReQueues": "",
|
||||
"browseWorkspacePath": "",
|
||||
"overlapPickerNote": ""
|
||||
},
|
||||
"scope": {
|
||||
"globalBanner": "这些设置在所有 Fusion 项目中共享。",
|
||||
@@ -6335,7 +6433,12 @@
|
||||
"worktrunk": "",
|
||||
"worktrunkBinaryPath": "",
|
||||
"worktrunkFailureBehavior": "",
|
||||
"worktrunkIntegration": ""
|
||||
"worktrunkIntegration": "",
|
||||
"worktreesPickerNote": ""
|
||||
},
|
||||
"fileBrowser": {
|
||||
"currentDirectory": "",
|
||||
"projectRoot": ""
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
@@ -6655,7 +6758,10 @@
|
||||
"withoutGitHub1": "手动创建任务",
|
||||
"withoutGitHub2": "为 AI 代理描述工作",
|
||||
"withoutGitHub3": "在看板上跟踪进度",
|
||||
"withoutGitHubHeading": "不使用 GitHub(现在可用):"
|
||||
"withoutGitHubHeading": "不使用 GitHub(现在可用):",
|
||||
"brandLogo": "",
|
||||
"brandName": "",
|
||||
"setupCompleteTitle": ""
|
||||
},
|
||||
"shell": {
|
||||
"activePill": "活跃",
|
||||
@@ -7201,7 +7307,8 @@
|
||||
"mergingFixes": "正在合并修复…",
|
||||
"mergingPr": "正在合并 PR…",
|
||||
"startPrReview": "开始 PR 审查",
|
||||
"statusRefreshed": "PR 状态已刷新"
|
||||
"statusRefreshed": "PR 状态已刷新",
|
||||
"label": ""
|
||||
},
|
||||
"priority": {
|
||||
"ariaLabel": "任务优先级",
|
||||
@@ -7216,7 +7323,8 @@
|
||||
},
|
||||
"provenance": {
|
||||
"createdBy": "创建者:",
|
||||
"createdVia": "通过…创建"
|
||||
"createdVia": "通过…创建",
|
||||
"parentTaskOf": ""
|
||||
},
|
||||
"recoveryState": "恢复状态",
|
||||
"refine": {
|
||||
@@ -7955,7 +8063,43 @@
|
||||
"switchToMarkdown": "切换到 Markdown",
|
||||
"switchToPlain": "切换到纯文本",
|
||||
"thinkingLevel": "",
|
||||
"workflowName": ""
|
||||
"workflowName": "",
|
||||
"aggregateAdvisory": "",
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"approveAndRun": "",
|
||||
"approveCommandError": "",
|
||||
"approving": "",
|
||||
"awaitingInputTitle": "",
|
||||
"cliApprovalRejectHint": "",
|
||||
"cliApprovalTitle": "",
|
||||
"cliApprovalWarning": "",
|
||||
"customWorkflowFallback": "",
|
||||
"customWorkflowLabel": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"inputPlaceholder": "",
|
||||
"modelDefault": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"replyInComments": "",
|
||||
"resumeTaskError": "",
|
||||
"resuming": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"submitAndResume": "",
|
||||
"submitting": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowColumns": {
|
||||
"add": "",
|
||||
@@ -8007,7 +8151,30 @@
|
||||
},
|
||||
"collapsePrompt": "",
|
||||
"editingPrompt": "",
|
||||
"expandPrompt": ""
|
||||
"expandPrompt": "",
|
||||
"agent": "",
|
||||
"agentsLoadFailed": "",
|
||||
"autoApproveRequests": "",
|
||||
"autoApproveRequestsNote": "",
|
||||
"cliCommandNote": "",
|
||||
"cliMode": "",
|
||||
"cliScript": "",
|
||||
"command": "",
|
||||
"executor": "",
|
||||
"maxRetries": "",
|
||||
"model": "",
|
||||
"modelsLoadFailed": "",
|
||||
"namedScript": "",
|
||||
"namedScriptNote": "",
|
||||
"prompt": "",
|
||||
"scriptName": "",
|
||||
"selectAgent": "",
|
||||
"selectSkill": "",
|
||||
"skill": "",
|
||||
"skillsLoadFailed": "",
|
||||
"skipFirstRunApproval": "",
|
||||
"waitForUserInput": "",
|
||||
"waitForUserInputNote": ""
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "添加字段",
|
||||
@@ -8141,7 +8308,11 @@
|
||||
"templatesPluginSteps": "插件步骤",
|
||||
"templatesSection": "模板",
|
||||
"timeoutMs": "",
|
||||
"trivialGraphHint": "此工作流仅运行开始→结束。从上方面板添加步骤以构建工作流。"
|
||||
"trivialGraphHint": "此工作流仅运行开始→结束。从上方面板添加步骤以构建工作流。",
|
||||
"conditionFailure": "",
|
||||
"conditionSuccess": "",
|
||||
"nodeInspector": "",
|
||||
"readOnlyDuplicateToEdit": ""
|
||||
},
|
||||
"workflows": {
|
||||
"aiEdit": "用 AI 设计",
|
||||
@@ -8204,13 +8375,27 @@
|
||||
"templateNodeCount_other": "{{count}} 个节点",
|
||||
"templatePickerLabel": "从…开始",
|
||||
"templateSectionBuiltin": "内置工作流",
|
||||
"templateSectionYours": "我的工作流"
|
||||
"templateSectionYours": "我的工作流",
|
||||
"closeEditor": "",
|
||||
"duplicatedEditable": "",
|
||||
"duplicateFailed": "",
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"noneYet": "",
|
||||
"title": ""
|
||||
},
|
||||
"workflowSelector": {
|
||||
"switchActiveMessage": "",
|
||||
"switchActiveTitle": "",
|
||||
"switchCancel": "",
|
||||
"switchConfirm": ""
|
||||
"switchConfirm": "",
|
||||
"applyFailed": "",
|
||||
"defaultCleared": "",
|
||||
"defaultSet": "",
|
||||
"defaultWorkflowLabel": "",
|
||||
"loadFailed": "",
|
||||
"manage": "",
|
||||
"none": ""
|
||||
},
|
||||
"workflowSettings": {
|
||||
"add": "",
|
||||
@@ -8288,5 +8473,50 @@
|
||||
"installRequestTitle": "Worktrunk 安装请求",
|
||||
"sha256": "SHA-256",
|
||||
"version": "版本"
|
||||
},
|
||||
"taskChat": {
|
||||
"activePlaceholder": "",
|
||||
"activeSessionHint": "",
|
||||
"agentMessages": "",
|
||||
"arguments": "",
|
||||
"collapseChat": "",
|
||||
"donePlaceholder": "",
|
||||
"doneSessionHint": "",
|
||||
"emptyAgentOutput": "",
|
||||
"entryCount_one": "",
|
||||
"entryCount_other": "",
|
||||
"error": "",
|
||||
"errorCount_one": "",
|
||||
"errorCount_other": "",
|
||||
"expandChat": "",
|
||||
"idleSessionHint": "",
|
||||
"jumpToLatestMessage": "",
|
||||
"latest": "",
|
||||
"loadingAgentOutput": "",
|
||||
"loadingEarlierMessages": "",
|
||||
"loadPreviousMessages": "",
|
||||
"message": "",
|
||||
"messageActiveAgentSession": "",
|
||||
"moreTools_one": "",
|
||||
"moreTools_other": "",
|
||||
"result": "",
|
||||
"roles": {
|
||||
"agent": "",
|
||||
"executor": "",
|
||||
"merger": "",
|
||||
"planner": "",
|
||||
"reviewer": ""
|
||||
},
|
||||
"sending": "",
|
||||
"thinking": "",
|
||||
"toolCall": "",
|
||||
"toolCallCount_one": "",
|
||||
"toolCallCount_other": "",
|
||||
"toolCallTo": "",
|
||||
"toolError": "",
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"actions": {
|
||||
"send": ""
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -78,6 +81,9 @@
|
||||
"offline": "",
|
||||
"online": ""
|
||||
},
|
||||
"labels": {
|
||||
"name": ""
|
||||
},
|
||||
"merge": {
|
||||
"unknown": ""
|
||||
},
|
||||
@@ -214,28 +220,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"workflow": {
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"customWorkflowFallback": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
|
||||
@@ -1181,7 +1181,11 @@
|
||||
"workflowMismatch": ""
|
||||
},
|
||||
"todo": "待辦",
|
||||
"triage": "分診"
|
||||
"triage": "分診",
|
||||
"workflow": {
|
||||
"edit": "",
|
||||
"new": ""
|
||||
}
|
||||
},
|
||||
"branchGroup": {
|
||||
"abandonGroup": "",
|
||||
@@ -1810,7 +1814,8 @@
|
||||
"heading": "評論",
|
||||
"placeholder": "新增評論",
|
||||
"postingButton": "發佈中…",
|
||||
"updatedSuccess": "評論已更新"
|
||||
"updatedSuccess": "評論已更新",
|
||||
"aiGuidance": ""
|
||||
},
|
||||
"commit": {
|
||||
"filesChanged_one": "",
|
||||
@@ -4686,7 +4691,60 @@
|
||||
"rerunPreflight": "重新執行飛行前檢查",
|
||||
"revertToAi": "恢復為 AI 版本",
|
||||
"titleLabel": "標題",
|
||||
"usingTemplate": "使用 <code>.github/pull_request_template.md</code>"
|
||||
"usingTemplate": "使用 <code>.github/pull_request_template.md</code>",
|
||||
"assignees": "",
|
||||
"dismissConflictResolutionError": "",
|
||||
"dismissPushBranchError": "",
|
||||
"error": {
|
||||
"actionOpen": "",
|
||||
"actionRun": "",
|
||||
"docs": ""
|
||||
},
|
||||
"generatingBody": "",
|
||||
"generatingTitle": "",
|
||||
"labels": "",
|
||||
"loadingOptions": "",
|
||||
"loadingPreflight": "",
|
||||
"noChangedFiles": "",
|
||||
"noCommits": "",
|
||||
"pushBranch": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"resolveConflicts": {
|
||||
"button": "",
|
||||
"message": "",
|
||||
"title": ""
|
||||
},
|
||||
"reviewers": "",
|
||||
"view": {
|
||||
"agentDisagreed": "",
|
||||
"agentReplyFix": "",
|
||||
"approve": "",
|
||||
"autoMerge": "",
|
||||
"close": "",
|
||||
"confirmMerge": "",
|
||||
"creating": "",
|
||||
"creationFailed": "",
|
||||
"loading": "",
|
||||
"merge": "",
|
||||
"mergeableLabel": "",
|
||||
"mergeDisabledUntilVerified": "",
|
||||
"none": "",
|
||||
"noReviewThreads": "",
|
||||
"requestRetry": "",
|
||||
"resolveConflictsBeforeMerge": "",
|
||||
"resolveConflictsOnGithub": "",
|
||||
"responseAlreadyInProgress": "",
|
||||
"responsePending_one": "",
|
||||
"responsePending_other": "",
|
||||
"retryCreation": "",
|
||||
"reviewLabel": "",
|
||||
"threadFixed": "",
|
||||
"threadPending": "",
|
||||
"verifyingGithub": ""
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"blockedDescription": "您可以在單獨的瀏覽器分頁中查看預覽。",
|
||||
@@ -5553,7 +5611,8 @@
|
||||
"totalSize": "",
|
||||
"view": "",
|
||||
"whenEnabledProjectAndAgentMemoryFilesAre": "",
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": ""
|
||||
"whenEnabledTheDatabaseIsBackedUpAutomatically": "",
|
||||
"createFailed": ""
|
||||
},
|
||||
"clearToDefault": "",
|
||||
"cliAgents": {
|
||||
@@ -5597,7 +5656,9 @@
|
||||
},
|
||||
"footer": {
|
||||
"help": "說明",
|
||||
"version": "版本 {{version}}"
|
||||
"version": "版本 {{version}}",
|
||||
"checkUpdates": "",
|
||||
"helpDiscussions": ""
|
||||
},
|
||||
"general": {
|
||||
"25": "",
|
||||
@@ -5746,7 +5807,10 @@
|
||||
"whenEnabledStartupRefreshesModelsThroughTheLocal": ""
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord"
|
||||
"discord": "Discord",
|
||||
"joinDiscord": "",
|
||||
"star": "",
|
||||
"starFusion": ""
|
||||
},
|
||||
"importExport": {
|
||||
"confirmImport": "確認匯入",
|
||||
@@ -5756,7 +5820,35 @@
|
||||
"importing": "匯入中…",
|
||||
"importTitle": "匯入設定",
|
||||
"loadingFile": "載入中…",
|
||||
"reviewPrompt": "查看要匯入的設定:"
|
||||
"reviewPrompt": "查看要匯入的設定:",
|
||||
"counts": {
|
||||
"global_one": "",
|
||||
"global_other": "",
|
||||
"project_one": "",
|
||||
"project_other": "",
|
||||
"workflowSettings_one": "",
|
||||
"workflowSettings_other": ""
|
||||
},
|
||||
"exported": "",
|
||||
"exportFailed": "",
|
||||
"globalSettings": "",
|
||||
"imported": "",
|
||||
"importFailed": "",
|
||||
"importFailedDetailed": "",
|
||||
"importScope": "",
|
||||
"importTitleAttr": "",
|
||||
"invalidJson": "",
|
||||
"mergeExisting": "",
|
||||
"projectSettings": "",
|
||||
"replaceWarning": "",
|
||||
"scopeBoth": "",
|
||||
"scopeGlobal": "",
|
||||
"scopeLabel": {
|
||||
"all": "",
|
||||
"global": "",
|
||||
"project": ""
|
||||
},
|
||||
"scopeProject": ""
|
||||
},
|
||||
"jsonPlaceholder": "輸入 JSON 值...",
|
||||
"keepLocal": "保留本機",
|
||||
@@ -5806,7 +5898,10 @@
|
||||
"searchMemoryWithQmd": "",
|
||||
"testing": "測試中…",
|
||||
"testRetrieval": "測試擷取",
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": ""
|
||||
"turnsDailyNotesIntoDREAMSMdAndPromotes": "",
|
||||
"qmdInstalled": "",
|
||||
"qmdInstallFailed": "",
|
||||
"qmdInstallUnavailable": ""
|
||||
},
|
||||
"merge": {
|
||||
"abort": "",
|
||||
@@ -6155,7 +6250,8 @@
|
||||
"uRLAndQRGenerationUseTheSelectedToken": "",
|
||||
"uRLNoHostnameOrPortConfigurationNeeded": "",
|
||||
"useExisting": "使用現有",
|
||||
"usingQuickTunnel": ""
|
||||
"usingQuickTunnel": "",
|
||||
"installationFailed": ""
|
||||
},
|
||||
"researchGlobal": {
|
||||
"advancedExternalSearchProviders": "",
|
||||
@@ -6275,7 +6371,9 @@
|
||||
"timeoutInMinutesForDetectingStuckTasksWhen": "",
|
||||
"whenEnabledTasksThatModifyTheSameFiles": "",
|
||||
"whenEnabledTasksWithStalePlansPROMPTMd": "",
|
||||
"whenTheStuckDetectorKillsAndReQueues": ""
|
||||
"whenTheStuckDetectorKillsAndReQueues": "",
|
||||
"browseWorkspacePath": "",
|
||||
"overlapPickerNote": ""
|
||||
},
|
||||
"scope": {
|
||||
"globalBanner": "這些設定在所有 Fusion 專案中共用。",
|
||||
@@ -6335,7 +6433,12 @@
|
||||
"worktrunk": "",
|
||||
"worktrunkBinaryPath": "",
|
||||
"worktrunkFailureBehavior": "",
|
||||
"worktrunkIntegration": ""
|
||||
"worktrunkIntegration": "",
|
||||
"worktreesPickerNote": ""
|
||||
},
|
||||
"fileBrowser": {
|
||||
"currentDirectory": "",
|
||||
"projectRoot": ""
|
||||
}
|
||||
},
|
||||
"setup": {
|
||||
@@ -6655,7 +6758,10 @@
|
||||
"withoutGitHub1": "手動建立任務",
|
||||
"withoutGitHub2": "為 AI 代理描述工作",
|
||||
"withoutGitHub3": "在看板上追蹤進度",
|
||||
"withoutGitHubHeading": "不使用 GitHub(現在可用):"
|
||||
"withoutGitHubHeading": "不使用 GitHub(現在可用):",
|
||||
"brandLogo": "",
|
||||
"brandName": "",
|
||||
"setupCompleteTitle": ""
|
||||
},
|
||||
"shell": {
|
||||
"activePill": "作用中",
|
||||
@@ -7201,7 +7307,8 @@
|
||||
"mergingFixes": "正在合併修復…",
|
||||
"mergingPr": "正在合併 PR…",
|
||||
"startPrReview": "開始 PR 審查",
|
||||
"statusRefreshed": "PR 狀態已刷新"
|
||||
"statusRefreshed": "PR 狀態已刷新",
|
||||
"label": ""
|
||||
},
|
||||
"priority": {
|
||||
"ariaLabel": "任務優先級",
|
||||
@@ -7216,7 +7323,8 @@
|
||||
},
|
||||
"provenance": {
|
||||
"createdBy": "創建者:",
|
||||
"createdVia": "通過…建立"
|
||||
"createdVia": "通過…建立",
|
||||
"parentTaskOf": ""
|
||||
},
|
||||
"recoveryState": "恢復狀態",
|
||||
"refine": {
|
||||
@@ -7955,7 +8063,43 @@
|
||||
"switchToMarkdown": "切換為 Markdown",
|
||||
"switchToPlain": "切換為純文字",
|
||||
"thinkingLevel": "",
|
||||
"workflowName": ""
|
||||
"workflowName": "",
|
||||
"aggregateAdvisory": "",
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"approveAndRun": "",
|
||||
"approveCommandError": "",
|
||||
"approving": "",
|
||||
"awaitingInputTitle": "",
|
||||
"cliApprovalRejectHint": "",
|
||||
"cliApprovalTitle": "",
|
||||
"cliApprovalWarning": "",
|
||||
"customWorkflowFallback": "",
|
||||
"customWorkflowLabel": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"inputPlaceholder": "",
|
||||
"modelDefault": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"replyInComments": "",
|
||||
"resumeTaskError": "",
|
||||
"resuming": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"submitAndResume": "",
|
||||
"submitting": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowColumns": {
|
||||
"add": "",
|
||||
@@ -8007,7 +8151,30 @@
|
||||
},
|
||||
"collapsePrompt": "",
|
||||
"editingPrompt": "",
|
||||
"expandPrompt": ""
|
||||
"expandPrompt": "",
|
||||
"agent": "",
|
||||
"agentsLoadFailed": "",
|
||||
"autoApproveRequests": "",
|
||||
"autoApproveRequestsNote": "",
|
||||
"cliCommandNote": "",
|
||||
"cliMode": "",
|
||||
"cliScript": "",
|
||||
"command": "",
|
||||
"executor": "",
|
||||
"maxRetries": "",
|
||||
"model": "",
|
||||
"modelsLoadFailed": "",
|
||||
"namedScript": "",
|
||||
"namedScriptNote": "",
|
||||
"prompt": "",
|
||||
"scriptName": "",
|
||||
"selectAgent": "",
|
||||
"selectSkill": "",
|
||||
"skill": "",
|
||||
"skillsLoadFailed": "",
|
||||
"skipFirstRunApproval": "",
|
||||
"waitForUserInput": "",
|
||||
"waitForUserInputNote": ""
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "新增欄位",
|
||||
@@ -8141,7 +8308,11 @@
|
||||
"templatesPluginSteps": "外掛步驟",
|
||||
"templatesSection": "範本",
|
||||
"timeoutMs": "",
|
||||
"trivialGraphHint": "這個工作流程只會執行 start → end。請從上方的選盤新增步驟來建構它。"
|
||||
"trivialGraphHint": "這個工作流程只會執行 start → end。請從上方的選盤新增步驟來建構它。",
|
||||
"conditionFailure": "",
|
||||
"conditionSuccess": "",
|
||||
"nodeInspector": "",
|
||||
"readOnlyDuplicateToEdit": ""
|
||||
},
|
||||
"workflows": {
|
||||
"aiEdit": "使用 AI 設計",
|
||||
@@ -8204,13 +8375,27 @@
|
||||
"templateNodeCount_other": "{{count}} 個節點",
|
||||
"templatePickerLabel": "起始來源",
|
||||
"templateSectionBuiltin": "內建工作流程",
|
||||
"templateSectionYours": "你的工作流程"
|
||||
"templateSectionYours": "你的工作流程",
|
||||
"closeEditor": "",
|
||||
"duplicatedEditable": "",
|
||||
"duplicateFailed": "",
|
||||
"loadFailed": "",
|
||||
"loading": "",
|
||||
"noneYet": "",
|
||||
"title": ""
|
||||
},
|
||||
"workflowSelector": {
|
||||
"switchActiveMessage": "",
|
||||
"switchActiveTitle": "",
|
||||
"switchCancel": "",
|
||||
"switchConfirm": ""
|
||||
"switchConfirm": "",
|
||||
"applyFailed": "",
|
||||
"defaultCleared": "",
|
||||
"defaultSet": "",
|
||||
"defaultWorkflowLabel": "",
|
||||
"loadFailed": "",
|
||||
"manage": "",
|
||||
"none": ""
|
||||
},
|
||||
"workflowSettings": {
|
||||
"add": "",
|
||||
@@ -8288,5 +8473,50 @@
|
||||
"installRequestTitle": "Worktrunk 安裝請求",
|
||||
"sha256": "SHA-256",
|
||||
"version": "版本"
|
||||
},
|
||||
"taskChat": {
|
||||
"activePlaceholder": "",
|
||||
"activeSessionHint": "",
|
||||
"agentMessages": "",
|
||||
"arguments": "",
|
||||
"collapseChat": "",
|
||||
"donePlaceholder": "",
|
||||
"doneSessionHint": "",
|
||||
"emptyAgentOutput": "",
|
||||
"entryCount_one": "",
|
||||
"entryCount_other": "",
|
||||
"error": "",
|
||||
"errorCount_one": "",
|
||||
"errorCount_other": "",
|
||||
"expandChat": "",
|
||||
"idleSessionHint": "",
|
||||
"jumpToLatestMessage": "",
|
||||
"latest": "",
|
||||
"loadingAgentOutput": "",
|
||||
"loadingEarlierMessages": "",
|
||||
"loadPreviousMessages": "",
|
||||
"message": "",
|
||||
"messageActiveAgentSession": "",
|
||||
"moreTools_one": "",
|
||||
"moreTools_other": "",
|
||||
"result": "",
|
||||
"roles": {
|
||||
"agent": "",
|
||||
"executor": "",
|
||||
"merger": "",
|
||||
"planner": "",
|
||||
"reviewer": ""
|
||||
},
|
||||
"sending": "",
|
||||
"thinking": "",
|
||||
"toolCall": "",
|
||||
"toolCallCount_one": "",
|
||||
"toolCallCount_other": "",
|
||||
"toolCallTo": "",
|
||||
"toolError": "",
|
||||
"toolNames": "",
|
||||
"toolResult": "",
|
||||
"you": "",
|
||||
"youMessage": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
{
|
||||
"actions": {
|
||||
"send": ""
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "",
|
||||
@@ -78,6 +81,9 @@
|
||||
"offline": "",
|
||||
"online": ""
|
||||
},
|
||||
"labels": {
|
||||
"name": ""
|
||||
},
|
||||
"merge": {
|
||||
"unknown": ""
|
||||
},
|
||||
@@ -214,28 +220,6 @@
|
||||
"refreshSourceInitialLoad": "",
|
||||
"refreshSourceManual": ""
|
||||
},
|
||||
"workflow": {
|
||||
"aggregateAllPassed": "",
|
||||
"aggregateInProgress": "",
|
||||
"aggregateNoResults": "",
|
||||
"customWorkflowFallback": "",
|
||||
"defaultWorkflow": "",
|
||||
"executionAwaitingCliApproval": "",
|
||||
"executionAwaitingInput": "",
|
||||
"executionCompleted": "",
|
||||
"executionNotStarted": "",
|
||||
"executionPaused": "",
|
||||
"executionPostMerge": "",
|
||||
"executionPreMerge": "",
|
||||
"postMerge": "",
|
||||
"preMerge": "",
|
||||
"statusAdvisory": "",
|
||||
"statusFailed": "",
|
||||
"statusPassed": "",
|
||||
"statusRunning": "",
|
||||
"statusSkipped": "",
|
||||
"waitingForOutput": ""
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "",
|
||||
"summaryCodeDefault": "",
|
||||
|
||||
272
packages/i18n/src/resources.d.ts
vendored
272
packages/i18n/src/resources.d.ts
vendored
@@ -1183,7 +1183,11 @@ export default interface Resources {
|
||||
"workflowMismatch": "Drag can't move a card between workflows. Use the workflow switcher instead."
|
||||
},
|
||||
"todo": "To Do",
|
||||
"triage": "Triage"
|
||||
"triage": "Triage",
|
||||
"workflow": {
|
||||
"edit": "Edit workflows",
|
||||
"new": "New workflow"
|
||||
}
|
||||
},
|
||||
"branchGroup": {
|
||||
"abandonGroup": "Abandon group",
|
||||
@@ -1805,6 +1809,7 @@ export default interface Resources {
|
||||
"comments": {
|
||||
"addButton": "Add Comment",
|
||||
"addedSuccess": "Comment added",
|
||||
"aiGuidance": "AI Guidance",
|
||||
"deletedSuccess": "Comment deleted",
|
||||
"deletingButton": "Deleting…",
|
||||
"editedSuffix": "(edited)",
|
||||
@@ -4661,6 +4666,7 @@ export default interface Resources {
|
||||
"viewUnavailable": "Plugin view unavailable"
|
||||
},
|
||||
"pr": {
|
||||
"assignees": "Assignees",
|
||||
"authFail": "Run gh auth login and try again.",
|
||||
"authOk": "GitHub CLI auth is available.",
|
||||
"baseBranch": "Base branch",
|
||||
@@ -4680,15 +4686,67 @@ export default interface Resources {
|
||||
"createDraftPr": "Create draft PR",
|
||||
"createPr": "Create PR",
|
||||
"createTitle": "Create Pull Request",
|
||||
"dismissConflictResolutionError": "Dismiss conflict resolution error",
|
||||
"dismissError": "Dismiss PR error",
|
||||
"dismissPushBranchError": "Dismiss push branch error",
|
||||
"error": {
|
||||
"actionOpen": "Action: open",
|
||||
"actionRun": "Action: run",
|
||||
"docs": "docs"
|
||||
},
|
||||
"generatingBody": "Generating AI body…",
|
||||
"generatingTitle": "Generating AI title…",
|
||||
"labels": "Labels",
|
||||
"loadingOptions": "Loading PR options…",
|
||||
"loadingPreflight": "Loading pre-flight checks…",
|
||||
"noChangedFiles": "No changed files detected.",
|
||||
"noCommits": "No commits found.",
|
||||
"noConflicts": "No merge conflicts detected.",
|
||||
"preflightChecks": "Pre-flight checks",
|
||||
"previewTitle": "Diff & commit preview",
|
||||
"pushBranch": {
|
||||
"button": "Push branch to remote",
|
||||
"message": "Fusion will push this task's branch to origin so the PR can be created.",
|
||||
"title": "Push branch to remote"
|
||||
},
|
||||
"regenerate": "Regenerate",
|
||||
"rerunPreflight": "Re-run preflight",
|
||||
"resolveConflicts": {
|
||||
"button": "Resolve conflicts with AI",
|
||||
"message": "Fusion will use AI to resolve conflicts on this branch and push it.",
|
||||
"title": "Resolve conflicts with AI"
|
||||
},
|
||||
"revertToAi": "Revert to AI version",
|
||||
"reviewers": "Reviewers",
|
||||
"titleLabel": "Title",
|
||||
"usingTemplate": "Using <code>.github/pull_request_template.md</code>"
|
||||
"usingTemplate": "Using <code>.github/pull_request_template.md</code>",
|
||||
"view": {
|
||||
"agentDisagreed": "agent disagreed",
|
||||
"agentReplyFix": "Agent reply — fix {{sha}}",
|
||||
"approve": "Approve",
|
||||
"autoMerge": "Auto-merge",
|
||||
"close": "Close",
|
||||
"confirmMerge": "Confirm merge",
|
||||
"creating": "Creating PR…",
|
||||
"creationFailed": "PR creation failed",
|
||||
"loading": "Loading PR…",
|
||||
"merge": "Merge",
|
||||
"mergeDisabledUntilVerified": "Merge is disabled until GitHub verifies this PR",
|
||||
"mergeableLabel": "Mergeable:",
|
||||
"noReviewThreads": "No review threads.",
|
||||
"none": "none",
|
||||
"requestRetry": "Request retry",
|
||||
"resolveConflictsBeforeMerge": "Resolve conflicts on GitHub before merging",
|
||||
"resolveConflictsOnGithub": "Resolve conflicts on GitHub",
|
||||
"responseAlreadyInProgress": "A response run is already in progress",
|
||||
"responsePending_one": "Response run in progress — {{count}} threads pending",
|
||||
"responsePending_other": "Response run in progress — {{count}} threads pending",
|
||||
"retryCreation": "Retry PR creation",
|
||||
"reviewLabel": "Review:",
|
||||
"threadFixed": "fixed",
|
||||
"threadPending": "pending",
|
||||
"verifyingGithub": "Verifying with GitHub…"
|
||||
}
|
||||
},
|
||||
"preview": {
|
||||
"blockedDescription": "You can view the preview in a separate browser tab.",
|
||||
@@ -5527,6 +5585,7 @@ export default interface Resources {
|
||||
"backupS": " backup(s)",
|
||||
"backupScheduleCron": "Backup Schedule (Cron)",
|
||||
"backups": "backups",
|
||||
"createFailed": "Failed to create backup",
|
||||
"creating": "Creating…",
|
||||
"cronExpressionForBackupTimingDefault02": " Cron expression for backup timing. Default: 0 2 * * * (daily at 2 AM). Examples: 0 * * * * (hourly), 0 0 * * 0 (weekly), */15 * * * * (every 15 min) ",
|
||||
"cronExpressionForMemoryBackupTimingDefault0": "Cron expression for memory backup timing. Default: 0 3 * * * (daily at 3 AM).",
|
||||
@@ -5597,8 +5656,14 @@ export default interface Resources {
|
||||
"experimentalFeaturesAreEarlyCapabilitiesThatAreNot": " Experimental features are early capabilities that are not yet fully stable. Enable them to test new functionality, but be aware they may change or be removed. ",
|
||||
"featureFlags": "Feature Flags"
|
||||
},
|
||||
"fileBrowser": {
|
||||
"currentDirectory": "Current directory:",
|
||||
"projectRoot": "(project root)"
|
||||
},
|
||||
"footer": {
|
||||
"checkUpdates": "Check for updates",
|
||||
"help": "Help",
|
||||
"helpDiscussions": "Help and discussions",
|
||||
"version": "Version {{version}}"
|
||||
},
|
||||
"general": {
|
||||
@@ -5748,17 +5813,48 @@ export default interface Resources {
|
||||
"whenEnabledStartupRefreshesModelsThroughTheLocal": " When enabled, startup refreshes models through the local "
|
||||
},
|
||||
"header": {
|
||||
"discord": "Discord"
|
||||
"discord": "Discord",
|
||||
"joinDiscord": "Join our Discord",
|
||||
"star": "Star",
|
||||
"starFusion": "Star Fusion on GitHub"
|
||||
},
|
||||
"importExport": {
|
||||
"confirmImport": "Confirm Import",
|
||||
"counts": {
|
||||
"global_one": "{{count}} global",
|
||||
"global_other": "{{count}} global",
|
||||
"project_one": "{{count}} project",
|
||||
"project_other": "{{count}} project",
|
||||
"workflowSettings_one": "{{count}} workflow setting value",
|
||||
"workflowSettings_other": "{{count}} workflow setting value"
|
||||
},
|
||||
"exportBtn": "Export",
|
||||
"exportFailed": "Failed to export settings",
|
||||
"exportTitle": "Export settings to JSON file",
|
||||
"exported": "Settings exported ({{scope}} scope)",
|
||||
"globalSettings": "Global Settings:",
|
||||
"importBtn": "Import",
|
||||
"importFailed": "Import failed",
|
||||
"importFailedDetailed": "Failed to import settings",
|
||||
"importScope": "Import Scope:",
|
||||
"importTitle": "Import Settings",
|
||||
"importTitleAttr": "Import settings from JSON file",
|
||||
"imported": "Imported {{counts}} setting(s)",
|
||||
"importing": "Importing…",
|
||||
"invalidJson": "Invalid JSON file: {{error}}",
|
||||
"loadingFile": "Loading…",
|
||||
"reviewPrompt": "Review the settings to be imported:"
|
||||
"mergeExisting": "Merge with existing settings (recommended)",
|
||||
"projectSettings": "Project Settings:",
|
||||
"replaceWarning": "If unchecked, existing settings will be replaced with imported values.",
|
||||
"reviewPrompt": "Review the settings to be imported:",
|
||||
"scopeBoth": "Both global and project settings",
|
||||
"scopeGlobal": "Global settings only",
|
||||
"scopeLabel": {
|
||||
"all": "all",
|
||||
"global": "global",
|
||||
"project": "project"
|
||||
},
|
||||
"scopeProject": "Project settings only"
|
||||
},
|
||||
"jsonPlaceholder": "Enter JSON value...",
|
||||
"keepLocal": "Keep Local",
|
||||
@@ -5800,6 +5896,9 @@ export default interface Resources {
|
||||
"noMatchingMemoryFound": "No matching memory found.",
|
||||
"processDreamsFromDailyMemory": " Process dreams from daily memory ",
|
||||
"qmd": " qmd ",
|
||||
"qmdInstallFailed": "Failed to install qmd",
|
||||
"qmdInstallUnavailable": "qmd install finished, but qmd is still unavailable",
|
||||
"qmdInstalled": "qmd installed successfully",
|
||||
"qmdIsNotInstalledSearchWillUseLocal": " qmd is not installed. Search will use local files. Install indexed retrieval: ",
|
||||
"result": " result",
|
||||
"runsTheSameQmdBackedMemorySearchPath": "Runs the same qmd-backed memory_search path agents use.",
|
||||
@@ -6115,6 +6214,7 @@ export default interface Resources {
|
||||
"ifHomebrewIsUnavailable": "If Homebrew is unavailable: ",
|
||||
"ingressURL": "Ingress URL",
|
||||
"installCloudflared": "Install cloudflared",
|
||||
"installationFailed": "Installation failed",
|
||||
"installing": "Installing…",
|
||||
"lastShortLivedTokenExpiresAt": "Last short-lived token expires at ",
|
||||
"manualInstall": "Manual install: ",
|
||||
@@ -6235,6 +6335,7 @@ export default interface Resources {
|
||||
"archiveCompletedTasksAfterDays": "Archive Completed Tasks After (days)",
|
||||
"backlogNoTaskAutoClaimIsExecutorOnly": "Backlog/no-task auto-claim is executor-only by default. Enable to let engineer-role agents auto-claim unowned backlog tasks; explicit routing and delegation are unchanged. Default: off.",
|
||||
"browse": " Browse ",
|
||||
"browseWorkspacePath": "Browse workspace path",
|
||||
"closeParenPeriod": ").",
|
||||
"compactModeKeepsArchiveSizeLowWhilePreserving": "Compact mode keeps archive size low while preserving recent agent activity for context.",
|
||||
"compactSummaryAndRecentEntries": "Compact summary and recent entries",
|
||||
@@ -6261,6 +6362,7 @@ export default interface Resources {
|
||||
"off": "Off",
|
||||
"optionalFileOrDirectoryPathsToIgnoreWhen": " Optional file or directory paths to ignore when overlap serialization is enabled. Paths are project-relative (for example ",
|
||||
"or": " or ",
|
||||
"overlapPickerNote": "Choose a file to ignore directly, or navigate into a folder and select the current directory.",
|
||||
"pollIntervalMs": "Poll Interval (ms)",
|
||||
"preserveStepProgressOnStuckTaskRequeue": " Preserve step progress on stuck-task requeue ",
|
||||
"remove": " Remove ",
|
||||
@@ -6334,6 +6436,7 @@ export default interface Resources {
|
||||
"worktreeNamingStyle": "Worktree Naming Style",
|
||||
"worktrees": "Worktrees",
|
||||
"worktreesDirectory": "Worktrees Directory",
|
||||
"worktreesPickerNote": "Navigate to the folder where Fusion should create task worktrees, then select the current directory.",
|
||||
"worktrunk": " worktrunk ",
|
||||
"worktrunkBinaryPath": "Worktrunk binary path",
|
||||
"worktrunkFailureBehavior": "Worktrunk failure behavior",
|
||||
@@ -6362,6 +6465,8 @@ export default interface Resources {
|
||||
"authToken": "Auth Token",
|
||||
"authTokenOptional": "Auth token (optional)",
|
||||
"back": "← Back",
|
||||
"brandLogo": "Fusion logo",
|
||||
"brandName": "Fusion",
|
||||
"browserAuthToken": "Browser Auth Token",
|
||||
"cancelLogin": "Cancel",
|
||||
"childProcess": "Child-Process",
|
||||
@@ -6602,6 +6707,7 @@ export default interface Resources {
|
||||
"setUpAi": "Set Up AI",
|
||||
"setUpProject": "Set Up Project",
|
||||
"setupComplete": "Setup complete! Head to the board to create your first task, or explore the dashboard to see what's available.",
|
||||
"setupCompleteTitle": "Setup Complete!",
|
||||
"setupMode": "Setup Mode",
|
||||
"setupWizardHint": "In the setup wizard, pick an existing directory or paste a GitHub clone URL.",
|
||||
"skip": "Skip",
|
||||
@@ -6925,6 +7031,51 @@ export default interface Resources {
|
||||
"toggleWordWrap": "Toggle word wrap",
|
||||
"unavailable": "Detailed file changes unavailable."
|
||||
},
|
||||
"taskChat": {
|
||||
"activePlaceholder": "Steer the currently executing agent",
|
||||
"activeSessionHint": "Message the active agent session. Guidance is delivered to the running session in real time.",
|
||||
"agentMessages": "{{label}} messages",
|
||||
"arguments": "Arguments",
|
||||
"collapseChat": "Collapse chat",
|
||||
"donePlaceholder": "Start a refinement task for this completed task",
|
||||
"doneSessionHint": "Send a message to start a refinement task for this completed task.",
|
||||
"emptyAgentOutput": "No agent output yet. Live messages from Planner, Executor, Reviewer, and Merger agents will appear here.",
|
||||
"entryCount_one": "{{count}} entry",
|
||||
"entryCount_other": "{{count}} entries",
|
||||
"error": "Error",
|
||||
"errorCount_one": "{{count}} error",
|
||||
"errorCount_other": "{{count}} errors",
|
||||
"expandChat": "Expand chat to full modal",
|
||||
"idleSessionHint": "No agent is working on this task right now. Your message is saved as guidance and will reach an agent the next time this task runs.",
|
||||
"jumpToLatestMessage": "Jump to latest message",
|
||||
"latest": "Latest",
|
||||
"loadPreviousMessages": "Load previous messages",
|
||||
"loadingAgentOutput": "Loading agent output…",
|
||||
"loadingEarlierMessages": "Loading earlier messages…",
|
||||
"message": "Message",
|
||||
"messageActiveAgentSession": "Message active agent session",
|
||||
"moreTools_one": ", +{{count}} more",
|
||||
"moreTools_other": ", +{{count}} more",
|
||||
"result": "Result",
|
||||
"roles": {
|
||||
"agent": "Agent",
|
||||
"executor": "Executor",
|
||||
"merger": "Merger",
|
||||
"planner": "Planner",
|
||||
"reviewer": "Reviewer"
|
||||
},
|
||||
"sending": "Sending",
|
||||
"thinking": "Thinking",
|
||||
"toolCall": "Tool call",
|
||||
"toolCallCount_one": "{{count}} tool call",
|
||||
"toolCallCount_other": "{{count}} tool calls",
|
||||
"toolCallTo": "Tool call → {{label}}",
|
||||
"toolError": "Tool error",
|
||||
"toolNames": "Tool names",
|
||||
"toolResult": "Tool result",
|
||||
"you": "You",
|
||||
"youMessage": "You message"
|
||||
},
|
||||
"taskDetail": {
|
||||
"actions": {
|
||||
"menuBtn": "Actions"
|
||||
@@ -7199,6 +7350,7 @@ export default interface Resources {
|
||||
"checkPrStatus": "Check PR Status",
|
||||
"creatingPr": "Creating PR…",
|
||||
"finishAndClose": "Finish & Close",
|
||||
"label": "PR",
|
||||
"mergeAndClose": "Merge & Close",
|
||||
"mergingFixes": "Merging fixes…",
|
||||
"mergingPr": "Merging PR…",
|
||||
@@ -7218,7 +7370,8 @@ export default interface Resources {
|
||||
},
|
||||
"provenance": {
|
||||
"createdBy": "Created by",
|
||||
"createdVia": "Created via"
|
||||
"createdVia": "Created via",
|
||||
"parentTaskOf": "of"
|
||||
},
|
||||
"recoveryState": "Recovery state",
|
||||
"refine": {
|
||||
@@ -7903,22 +8056,45 @@ export default interface Resources {
|
||||
},
|
||||
"workflow": {
|
||||
"advisoryExplanation": "Advisory workflow steps flagged non-blocking improvements:",
|
||||
"aggregateAdvisory": "Advisory",
|
||||
"aggregateAllPassed": "All passed",
|
||||
"aggregateInProgress": "In progress",
|
||||
"aggregateNoResults": "No results",
|
||||
"aggregateResult": "Aggregate result",
|
||||
"approveAndRun": "Approve & run",
|
||||
"approveCommandError": "Failed to approve command",
|
||||
"approving": "Approving…",
|
||||
"awaitingInputTitle": "Waiting for your input",
|
||||
"cliApprovalRejectHint": "To reject, keep the task paused and do not approve.",
|
||||
"cliApprovalTitle": "Approve CLI command?",
|
||||
"cliApprovalWarning": "This command will run in the task worktree. Approving trusts this exact command for future runs.",
|
||||
"configuredSteps": "Configured Workflow Steps",
|
||||
"customWorkflowFallback": "Custom workflow",
|
||||
"customWorkflowLabel": "Custom workflow",
|
||||
"defaultWorkflow": "Default",
|
||||
"done": "Done",
|
||||
"doneEditingAriaLabel": "Done editing workflow steps",
|
||||
"edit": "Edit",
|
||||
"editAriaLabel": "Edit workflow steps",
|
||||
"editWorkflow": "Edit workflow",
|
||||
"executionAwaitingCliApproval": "Awaiting CLI approval",
|
||||
"executionAwaitingInput": "Awaiting input",
|
||||
"executionCompleted": "Completed",
|
||||
"executionNotStarted": "Not started",
|
||||
"executionOrder": "Execution order:",
|
||||
"executionPaused": "Paused",
|
||||
"executionPhase": "Execution phase",
|
||||
"executionPostMerge": "Post-merge steps running",
|
||||
"executionPreMerge": "Pre-merge steps running",
|
||||
"expandOutput": "Expand output",
|
||||
"graph": "Workflow graph",
|
||||
"graphUnavailable": "Workflow graph unavailable",
|
||||
"hideOutput": "Hide output",
|
||||
"inputPlaceholder": "Type your reply…",
|
||||
"loadingGraph": "Loading workflow graph…",
|
||||
"loadingResults": "Loading workflow results…",
|
||||
"markdown": "Markdown",
|
||||
"modelDefault": "Default",
|
||||
"modelSettings": "Model settings",
|
||||
"moveDown": "Move down",
|
||||
"moveUp": "Move up",
|
||||
@@ -7930,10 +8106,20 @@ export default interface Resources {
|
||||
"overview": "Workflow overview",
|
||||
"plain": "Plain",
|
||||
"polishNotes": "Polish notes",
|
||||
"postMerge": "Post-merge",
|
||||
"preMerge": "Pre-merge",
|
||||
"remove": "Remove",
|
||||
"replyInComments": "Reply in the comments and unpause the task to continue.",
|
||||
"resumeTaskError": "Failed to resume task",
|
||||
"resuming": "Resuming…",
|
||||
"selectStepsDescription": "Select steps to run after task implementation completes",
|
||||
"showOutput": "Show output",
|
||||
"started": "Started:",
|
||||
"statusAdvisory": "Advisory failure",
|
||||
"statusFailed": "Failed",
|
||||
"statusPassed": "Passed",
|
||||
"statusRunning": "Running…",
|
||||
"statusSkipped": "Skipped",
|
||||
"stepCount_one": "{{count}} step",
|
||||
"stepCount_other": "{{count}} steps",
|
||||
"stepDefinitionNotFound": "Step definition not found.",
|
||||
@@ -7941,6 +8127,8 @@ export default interface Resources {
|
||||
"stepProgressValue": "{{completed}} of {{total}} steps completed",
|
||||
"steps": "Workflow Steps",
|
||||
"stepsExplanation": "Pre-merge steps run after implementation, before merge. Post-merge steps run after merge succeeds.",
|
||||
"submitAndResume": "Submit & resume",
|
||||
"submitting": "Submitting…",
|
||||
"summaryAdvisory_one": "{{count}} advisory",
|
||||
"summaryAdvisory_other": "{{count}} advisory",
|
||||
"summaryFailed_one": "{{count}} failed",
|
||||
@@ -7957,6 +8145,7 @@ export default interface Resources {
|
||||
"switchToMarkdown": "Switch to markdown",
|
||||
"switchToPlain": "Switch to plain text",
|
||||
"thinkingLevel": "Thinking level",
|
||||
"waitingForOutput": "Waiting for agent output…",
|
||||
"workflowName": "Workflow"
|
||||
},
|
||||
"workflowColumns": {
|
||||
@@ -7993,6 +8182,10 @@ export default interface Resources {
|
||||
"unplacedCount_other": "{{count}} nodes not placed in a column"
|
||||
},
|
||||
"workflowEditor": {
|
||||
"agent": "Agent",
|
||||
"agentsLoadFailed": "Failed to load agents",
|
||||
"autoApproveRequests": "Auto-approve requests",
|
||||
"autoApproveRequestsNote": "Runs without pausing for approval — e.g. a CLI command executes on its first run without waiting for your sign-off.",
|
||||
"cliAgent": {
|
||||
"adapterLabel": "CLI adapter",
|
||||
"adapterNote": "Drives a CLI coding agent in an engine-owned terminal for this step.",
|
||||
@@ -8007,9 +8200,28 @@ export default interface Resources {
|
||||
"notifyLabel": "Waiting-on-input notification",
|
||||
"notifyNote": "How you are alerted when the agent pauses waiting for input on this step."
|
||||
},
|
||||
"cliCommandNote": "Runs an arbitrary command in the task worktree. The first time this exact command runs, the task pauses for your approval. The node prompt is passed via FUSION_NODE_PROMPT.",
|
||||
"cliMode": "CLI mode",
|
||||
"cliScript": "CLI / script",
|
||||
"collapsePrompt": "Collapse prompt editor",
|
||||
"command": "Command",
|
||||
"editingPrompt": "Editing Prompt",
|
||||
"expandPrompt": "Expand prompt editor"
|
||||
"executor": "Executor",
|
||||
"expandPrompt": "Expand prompt editor",
|
||||
"maxRetries": "Max retries",
|
||||
"model": "Model",
|
||||
"modelsLoadFailed": "Failed to load models",
|
||||
"namedScript": "Named script",
|
||||
"namedScriptNote": "Named script from project settings. The node prompt is passed via FUSION_NODE_PROMPT.",
|
||||
"prompt": "Prompt",
|
||||
"scriptName": "Script name",
|
||||
"selectAgent": "— select agent —",
|
||||
"selectSkill": "— select skill —",
|
||||
"skill": "Skill",
|
||||
"skillsLoadFailed": "Failed to load skills",
|
||||
"skipFirstRunApproval": "Skip first-run approval (runs without pausing)",
|
||||
"waitForUserInput": "Wait for user input",
|
||||
"waitForUserInputNote": "This node pauses the task until you reply in the task's comments and unpause. The Prompt field above is shown to the user as the question."
|
||||
},
|
||||
"workflowFields": {
|
||||
"add": "Add field",
|
||||
@@ -8052,6 +8264,8 @@ export default interface Resources {
|
||||
"codeSource": "Source (TypeScript)",
|
||||
"codeTimeout": "Timeout (ms)",
|
||||
"collapseInspector": "Collapse",
|
||||
"conditionFailure": "failure",
|
||||
"conditionSuccess": "success",
|
||||
"cycleBlocked": "That connection would create a cycle — only rework edges inside a for-each template may loop back",
|
||||
"deleteEdge": "Delete edge",
|
||||
"deleteNode": "Delete node",
|
||||
@@ -8108,6 +8322,7 @@ export default interface Resources {
|
||||
"mobileMoveDown": "Move down",
|
||||
"mobileMoveUp": "Move up",
|
||||
"mobileNodeKinds": "Node types",
|
||||
"nodeInspector": "Node",
|
||||
"notifyCustom": "Custom",
|
||||
"notifyCustomEvent": "Custom event",
|
||||
"notifyEvent": "Event type",
|
||||
@@ -8117,6 +8332,7 @@ export default interface Resources {
|
||||
"parseArtifact": "Artifact",
|
||||
"parseParser": "Parser",
|
||||
"quorumN": "Quorum count (n)",
|
||||
"readOnlyDuplicateToEdit": "Read-only built-in — duplicate the workflow to edit nodes.",
|
||||
"releaseCapacity": "Downstream capacity",
|
||||
"releaseCondition": "Release condition",
|
||||
"releaseDependency": "Dependency complete",
|
||||
@@ -8146,6 +8362,13 @@ export default interface Resources {
|
||||
"trivialGraphHint": "This workflow only runs start → end. Add steps from the palette above to build it out."
|
||||
},
|
||||
"workflowSelector": {
|
||||
"applyFailed": "Failed to apply workflow",
|
||||
"defaultCleared": "Default workflow cleared",
|
||||
"defaultSet": "Default workflow set",
|
||||
"defaultWorkflowLabel": "Default workflow for new tasks",
|
||||
"loadFailed": "Failed to load workflows",
|
||||
"manage": "Manage…",
|
||||
"none": "None",
|
||||
"switchActiveMessage": "This task has an active session. Switching workflows aborts it and re-homes the card into the new workflow's entry column. Continue?",
|
||||
"switchActiveTitle": "Switch workflow?",
|
||||
"switchCancel": "Cancel",
|
||||
@@ -8226,6 +8449,7 @@ export default interface Resources {
|
||||
"backToWorkflowList": "Back to workflows",
|
||||
"clickToEditDescription": "Click to edit description",
|
||||
"clickToRename": "Click to rename",
|
||||
"closeEditor": "Close workflow editor",
|
||||
"createDescription": "Description (optional)",
|
||||
"createFailed": "Failed to create workflow",
|
||||
"createName": "Name",
|
||||
@@ -8242,7 +8466,9 @@ export default interface Resources {
|
||||
"discardConfirm": "Discard",
|
||||
"discardMessage": "You have unsaved changes to this workflow. Discard them?",
|
||||
"discardTitle": "Discard unsaved changes?",
|
||||
"duplicateFailed": "Failed to duplicate workflow",
|
||||
"duplicateToCustomize": "Duplicate to customize",
|
||||
"duplicatedEditable": "Duplicated to \"{{name}}\" — editable",
|
||||
"emptyDescription": "Workflows orchestrate the steps and gates that run around task execution. Create one to start arranging that flow.",
|
||||
"emptyTitle": "No workflow selected",
|
||||
"export": "Export",
|
||||
@@ -8255,11 +8481,14 @@ export default interface Resources {
|
||||
"importStripped": "Auto-approval flags were removed from imported nodes",
|
||||
"importTooltip": "Import a workflow from a JSON file",
|
||||
"imported": "Imported workflow \"{{name}}\"",
|
||||
"loadFailed": "Failed to load workflows",
|
||||
"loading": "Loading…",
|
||||
"migrationNotice": "Your legacy workflow steps were converted — find them as templates in the palette and as the \"Migrated steps\" workflow.",
|
||||
"mobileEditorNav": "Workflow editor sections",
|
||||
"mobileSelectNote": "Select a workflow to edit.",
|
||||
"nameLabel": "Workflow name",
|
||||
"newWorkflow": "New workflow",
|
||||
"noneYet": "No workflows yet.",
|
||||
"readOnlyBuiltin": "Read-only built-in workflow",
|
||||
"saveFailed": "Failed to save workflow",
|
||||
"saved": "Workflow saved",
|
||||
@@ -8273,7 +8502,8 @@ export default interface Resources {
|
||||
"templateNodeCount_other": "{{count}} nodes",
|
||||
"templatePickerLabel": "Start from",
|
||||
"templateSectionBuiltin": "Built-in workflows",
|
||||
"templateSectionYours": "Your workflows"
|
||||
"templateSectionYours": "Your workflows",
|
||||
"title": "Workflows"
|
||||
},
|
||||
"workspace": {
|
||||
"projectRoot": "Project Root",
|
||||
@@ -8519,6 +8749,9 @@ export default interface Resources {
|
||||
}
|
||||
},
|
||||
"common": {
|
||||
"actions": {
|
||||
"send": "Send"
|
||||
},
|
||||
"agents": {
|
||||
"ratings": {
|
||||
"trendDeclining": "↓ Declining",
|
||||
@@ -8598,6 +8831,9 @@ export default interface Resources {
|
||||
"offline": "Offline",
|
||||
"online": "Online"
|
||||
},
|
||||
"labels": {
|
||||
"name": "Name"
|
||||
},
|
||||
"merge": {
|
||||
"unknown": "Unknown"
|
||||
},
|
||||
@@ -8734,28 +8970,6 @@ export default interface Resources {
|
||||
"refreshSourceInitialLoad": "Initial load",
|
||||
"refreshSourceManual": "Manual"
|
||||
},
|
||||
"workflow": {
|
||||
"aggregateAllPassed": "All passed",
|
||||
"aggregateInProgress": "In progress",
|
||||
"aggregateNoResults": "No results",
|
||||
"customWorkflowFallback": "Custom workflow",
|
||||
"defaultWorkflow": "Default",
|
||||
"executionAwaitingCliApproval": "Awaiting CLI approval",
|
||||
"executionAwaitingInput": "Awaiting input",
|
||||
"executionCompleted": "Completed",
|
||||
"executionNotStarted": "Not started",
|
||||
"executionPaused": "Paused",
|
||||
"executionPostMerge": "Post-merge steps running",
|
||||
"executionPreMerge": "Pre-merge steps running",
|
||||
"postMerge": "Post-merge",
|
||||
"preMerge": "Pre-merge",
|
||||
"statusAdvisory": "Advisory failure",
|
||||
"statusFailed": "Failed",
|
||||
"statusPassed": "Passed",
|
||||
"statusRunning": "Running…",
|
||||
"statusSkipped": "Skipped",
|
||||
"waitingForOutput": "Waiting for agent output…"
|
||||
},
|
||||
"workflowNodes": {
|
||||
"summaryAwaitInput": "Waits for user input",
|
||||
"summaryCodeDefault": "TypeScript",
|
||||
|
||||
Reference in New Issue
Block a user