Files
fusion/packages/dashboard/app/components/CliBinaryPanel.tsx
gsxdsm b760fa0df9 FN-6769: localize remaining dashboard strings
Localize the remaining dashboard UI clusters and refresh the generated i18n resources.

- Replace hardcoded labels across agent, plugin, mission, workflow node, research, document, activity, and miscellaneous dashboard components with app namespace translations.
- Add and synchronize locale catalog entries for English, Spanish, French, Korean, Simplified Chinese, and Traditional Chinese.
- Update i18n lint configuration and generated resource types, with a patch changeset for the published CLI package.

Files changed:
 .changeset/fn-6769-i18n-cluster.md                 |   5 +
 i18next.config.ts                                  |  47 +--
 .../dashboard/app/components/ActiveAgentsPanel.tsx |   2 +-
 packages/dashboard/app/components/ActivityFeed.tsx |  12 +-
 .../dashboard/app/components/ActivityLogModal.tsx  |   4 +-
 packages/dashboard/app/components/AddNodeModal.tsx |   4 +-
 .../dashboard/app/components/AgentDetailView.tsx   |   6 +-
 .../app/components/AgentGenerationModal.tsx        |   2 +-
 .../dashboard/app/components/AgentImportModal.tsx  |   4 +-
 .../app/components/AgentOnboardingModal.tsx        |   4 +-
 .../app/components/AgentPromptsManager.tsx         |   2 +-
 .../dashboard/app/components/AgentRunHistory.tsx   |   2 +-
 .../dashboard/app/components/AgentsOverviewBar.tsx |   2 +-
 .../dashboard/app/components/BranchGroupCard.tsx   |   4 +-
 .../dashboard/app/components/CliBinaryPanel.tsx    |   8 +-
 .../app/components/CursorCliProviderCard.tsx       |   4 +-
 .../dashboard/app/components/DashboardLoader.tsx   |   4 +-
 .../dashboard/app/components/DevServerView.tsx     |   8 +-
 .../dashboard/app/components/DocumentsView.tsx     |   6 +-
 .../dashboard/app/components/ErrorBoundary.tsx     |   9 +-
 .../dashboard/app/components/ExecutorStatusBar.tsx |   5 +-
 .../dashboard/app/components/GitHubStarPrompt.tsx  |  14 +-
 .../dashboard/app/components/GitManagerModal.tsx   |   4 +-
 .../dashboard/app/components/GroupTaskModal.tsx    |   2 +-
 packages/dashboard/app/components/Header.tsx       |   4 +-
 .../dashboard/app/components/HermesRuntimeCard.tsx |   2 +-
 packages/dashboard/app/components/InsightsView.tsx |   2 +-
 .../dashboard/app/components/IssueMentionPopup.tsx |   5 +-
 packages/dashboard/app/components/MailboxView.tsx  |   6 +-
 .../components/MilestoneSliceInterviewModal.tsx    |   2 +-
 .../app/components/MissionInterviewModal.tsx       |   2 +-
 .../dashboard/app/components/MissionManager.tsx    |   8 +-
 .../app/components/ModelOnboardingModal.tsx        |   2 +-
 .../dashboard/app/components/NodeDetailModal.tsx   |   2 +-
 .../app/components/PiExtensionsManager.tsx         |  10 +-
 .../dashboard/app/components/ProjectSelector.tsx   |   6 +-
 packages/dashboard/app/components/QuickChatFAB.tsx |  44 +--
 packages/dashboard/app/components/ResearchView.tsx |   2 +-
 packages/dashboard/app/components/RoutineCard.tsx  |   6 +-
 .../dashboard/app/components/RoutineEditor.tsx     |   2 +-
 packages/dashboard/app/components/RoutingTab.tsx   |   2 +-
 packages/dashboard/app/components/ScheduleCard.tsx |   6 +-
 packages/dashboard/app/components/ScheduleForm.tsx |   2 +-
 packages/dashboard/app/components/ScriptsModal.tsx |   2 +-
 .../app/components/StashConflictModal.tsx          |   4 +-
 .../dashboard/app/components/TerminalModal.tsx     |   2 +-
 .../app/components/nodes/WorkflowNodeTypes.tsx     |  47 +--
 packages/i18n/locales/en/app.json                  | 274 +++++++--------
 packages/i18n/locales/es/app.json                  | 144 ++++++--
 packages/i18n/locales/fr/app.json                  | 144 ++++++--
 packages/i18n/locales/ko/app.json                  | 144 ++++++--
 packages/i18n/locales/zh-CN/app.json               | 144 ++++++--
 packages/i18n/locales/zh-TW/app.json               | 144 ++++++--
 packages/i18n/src/resources.d.ts                   | 375 +++++++++++++++++++--
 54 files changed, 1261 insertions(+), 442 deletions(-)

Fusion-Task-Id: FN-6769

Fusion-Task-Lineage: a8733dc9-3b48-47e6-88c4-020f83ab41de
2026-06-20 05:04:25 -07:00

215 lines
7.7 KiB
TypeScript

import { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import {
fetchFnBinaryStatus,
installFnBinary,
type FnBinaryInstallResult,
type FnBinaryStatus,
} from "../api/legacy";
import "./CliBinaryPanel.css";
interface Props {
/**
* When true, the panel is mounted but should not auto-fetch on render.
* Used by the first-launch banner so it can show a button without
* forcing a probe before the user opts in.
*/
defer?: boolean;
}
// Note: These labels are fetched dynamically in the component via useTranslation
// to support i18n. See below for the actual label rendering.
/**
* Settings panel for the `fn` / `fusion` global CLI binary.
*
* Shows current install state, a one-click install button (runs
* `npm install -g runfusion.ai` server-side), and two copy-to-clipboard
* commands so users with non-default npm setups can install themselves.
*/
export function CliBinaryPanel({ defer = false }: Props) {
const { t } = useTranslation("app");
const [status, setStatus] = useState<FnBinaryStatus | null>(null);
const [loading, setLoading] = useState(false);
const [installing, setInstalling] = useState(false);
const [installResult, setInstallResult] = useState<FnBinaryInstallResult | null>(null);
const [error, setError] = useState<string | null>(null);
const [copied, setCopied] = useState<string | null>(null);
const getStateLabel = (state: FnBinaryStatus["state"]): { text: string; tone: "ok" | "warn" | "err" } => {
switch (state) {
case "installed":
return { text: t("cliBinary.stateInstalled", "Installed"), tone: "ok" };
case "missing":
return { text: t("cliBinary.stateMissing", "Not installed"), tone: "err" };
case "version-mismatch":
return { text: t("cliBinary.stateVersionMismatch", "Version mismatch"), tone: "warn" };
case "skipped":
return { text: t("cliBinary.stateCheckDisabled", "Check disabled"), tone: "warn" };
}
};
const refresh = useCallback(async () => {
setLoading(true);
setError(null);
try {
const next = await fetchFnBinaryStatus();
setStatus(next);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
}
}, []);
useEffect(() => {
if (defer) return;
void refresh();
}, [defer, refresh]);
const onInstall = useCallback(async () => {
setInstalling(true);
setInstallResult(null);
setError(null);
try {
const response = await installFnBinary();
setStatus({
binary: response.binary,
expectedVersion: response.expectedVersion,
state: response.state,
install: response.install,
});
setInstallResult(response.installResult);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setInstalling(false);
}
}, []);
const copy = useCallback(async (label: string, value: string) => {
try {
await navigator.clipboard.writeText(value);
setCopied(label);
setTimeout(() => setCopied((c) => (c === label ? null : c)), 1500);
} catch {
// Clipboard API unavailable — leave button silent rather than throwing.
}
}, []);
const stateMeta = status ? getStateLabel(status.state) : null;
return (
<div className="cli-binary-panel">
<div className="cli-binary-header">
<h4 className="settings-section-heading">{t("cliBinary.heading", "CLI Binary")}</h4>
{stateMeta && (
<span className={`cli-binary-pill cli-binary-pill--${stateMeta.tone}`}>
{stateMeta.text}
</span>
)}
</div>
<small className="cli-binary-help">
{t("cliBinary.help", "Installing the global CLI lets you run fn and fusion from any terminal. Automations and scripts work without it via npx, but a global install is faster and more convenient.")}
</small>
{loading && !status && <p className="cli-binary-status-line">{t("cliBinary.checking", "Checking…")}</p>}
{status && (
<div className="cli-binary-detail">
{status.binary.installed ? (
<ul className="cli-binary-info-list">
<li>
<span>{t("cliBinary.binaryLabel", "Binary:")}</span>
<code>{status.binary.binary}</code>
</li>
{status.binary.path && (
<li>
<span>{t("cliBinary.pathLabel", "Path:")}</span>
<code>{status.binary.path}</code>
</li>
)}
<li>
<span>{t("cliBinary.versionLabel", "Version:")}</span>
<code>{status.binary.version ?? "unknown"}</code>
<span className="cli-binary-expected">
{t("cliBinary.expectedVersion", "(expected {{version}})", { version: status.expectedVersion })}
</span>
</li>
</ul>
) : (
<p className="cli-binary-status-line">
{t("cliBinary.notOnPath", "Neither fn nor fusion was found on PATH.")}
</p>
)}
<div className="cli-binary-actions">
<button
type="button"
className="cli-binary-install-btn"
onClick={onInstall}
disabled={installing}
>
{installing
? t("cliBinary.installing", "Installing…")
: status.binary.installed
? t("cliBinary.reinstall", "Reinstall")
: t("cliBinary.installWithNpm", "Install with npm")}
</button>
<button
type="button"
className="cli-binary-refresh-btn"
onClick={() => void refresh()}
disabled={loading || installing}
>
{t("cliBinary.refresh", "Refresh")}
</button>
</div>
<div className="cli-binary-commands">
<label>{t("cliBinary.orCopyLabel", "Or copy and run yourself:")}</label>
{[
{ label: "npm", command: status.install.npm },
{ label: "curl", command: status.install.curl },
].map(({ label, command }) => (
<div key={label} className="cli-binary-command-row">
<code>{command}</code>
<button
type="button"
onClick={() => void copy(label, command)}
className="cli-binary-copy-btn"
>
{copied === label ? t("cliBinary.copied", "Copied") : t("cliBinary.copy", "Copy")}
</button>
</div>
))}
</div>
</div>
)}
{installResult && (
<details className="cli-binary-install-log" open={!installResult.success}>
<summary>
{installResult.success
? t("cliBinary.succeededDuration", "Install succeeded in {{duration}}s", { duration: (installResult.durationMs / 1000).toFixed(1) })
: t("cliBinary.failedExit", "Install failed (exit {{code}})", { code: installResult.exitCode ?? "n/a" })}
</summary>
{installResult.permissionsHint && (
<p className="cli-binary-permissions-hint">{installResult.permissionsHint}</p>
)}
{installResult.stdout && (
<pre className="cli-binary-install-output">{installResult.stdout}</pre>
)}
{installResult.stderr && (
<pre className="cli-binary-install-output cli-binary-install-output--err">
{installResult.stderr}
</pre>
)}
</details>
)}
{error && <p className="field-error">{error}</p>}
</div>
);
}