Migration (multi-agent sweep over 216 files, 60 batches):
- Every user-visible dashboard + TUI string moved to t() with the exact
English inline default (en rendering byte-identical)
- Catalogs merged from per-batch fragments: en/zh-CN/zh-TW/fr/es now
carry ~5,930 keys each across common/app/errors/cli namespaces;
CLI bundles regenerated (6 locales incl. ko)
Integration fixes:
- 18 type errors: reserved {{count}} interpolations renamed, malformed
plural call, hand-rolled t-param types replaced with TFunction<"app">
- 23 lint errors: superseded label constants/helpers removed
- ExecutorStatusBar hook-order violation (keyboard-open early return
moved below hooks)
- TUI tests wrapped in I18nextProvider (uninitialized fallback renders
literal {{placeholders}}); dashboard vitest.setup boots a minimal en
i18next instance for the same reason
Known WIP (next commits): ~457 residual strings across 50 batches,
Korean drafts for swept keys, and a dashboard test-suite pass that is
still being stabilized (~283 failures under investigation — fake-timer
waitFor interaction, likely stale node_modules vs merged lockfile).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
117 lines
3.7 KiB
TypeScript
117 lines
3.7 KiB
TypeScript
import type { ComponentType, ReactNode } from "react";
|
|
import { useTranslation } from "react-i18next";
|
|
import type { PluginUiSlotEntry } from "../api";
|
|
import { DroidCliProviderCard } from "../components/DroidCliProviderCard";
|
|
|
|
export interface PluginSlotHostActions {
|
|
refreshAuthProviders?: () => void;
|
|
openSettingsSection?: (section: string) => void;
|
|
openModelOnboarding?: () => void;
|
|
}
|
|
|
|
interface PluginSlotComponentProps {
|
|
entry: PluginUiSlotEntry;
|
|
actions?: PluginSlotHostActions;
|
|
}
|
|
|
|
interface PluginSlotRegistration {
|
|
pluginId: string;
|
|
slotId: string;
|
|
componentPath: string;
|
|
component: ComponentType<PluginSlotComponentProps>;
|
|
}
|
|
|
|
function DroidSettingsProviderCard({ actions }: PluginSlotComponentProps): ReactNode {
|
|
return (
|
|
<DroidCliProviderCard
|
|
compact
|
|
authenticated={false}
|
|
onToggled={() => {
|
|
actions?.refreshAuthProviders?.();
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function DroidOnboardingProviderCard({ actions }: PluginSlotComponentProps): ReactNode {
|
|
return (
|
|
<DroidCliProviderCard
|
|
authenticated={false}
|
|
onToggled={() => {
|
|
actions?.refreshAuthProviders?.();
|
|
}}
|
|
/>
|
|
);
|
|
}
|
|
|
|
function DroidOnboardingSetupHelp(): ReactNode {
|
|
const { t } = useTranslation("app");
|
|
return (
|
|
<p className="onboarding-helper-text" data-testid="droid-onboarding-setup-help">
|
|
{t("plugins.droidOnboardingTip", "Tip: Enable Droid CLI to reuse your Factory AI subscription without adding an API key.")}
|
|
</p>
|
|
);
|
|
}
|
|
|
|
function DroidPostOnboardingRecommendation({ actions }: PluginSlotComponentProps): ReactNode {
|
|
const { t } = useTranslation("app");
|
|
return (
|
|
<div className="post-onboarding-recommendations__item" data-testid="droid-post-onboarding-recommendation">
|
|
<span className="post-onboarding-recommendations__item-text">
|
|
<strong>{t("plugins.droidRecommendTitle", "Enable Droid CLI")}</strong>
|
|
<span>{t("plugins.droidRecommendDesc", "Use your local Droid CLI session as an AI provider in Fusion.")}</span>
|
|
</span>
|
|
<button type="button" className="btn btn-sm" onClick={() => actions?.openSettingsSection?.("authentication")}>
|
|
{t("plugins.openAuthentication", "Open Authentication")}
|
|
</button>
|
|
<button type="button" className="btn btn-sm" onClick={() => actions?.openModelOnboarding?.()}>
|
|
{t("plugins.openOnboarding", "Open Onboarding")}
|
|
</button>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const REGISTRY: PluginSlotRegistration[] = [
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "settings-provider-card",
|
|
componentPath: "./components/settings-provider-card.js",
|
|
component: DroidSettingsProviderCard,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "settings-integration-card",
|
|
componentPath: "./components/settings-integration-card.js",
|
|
component: DroidSettingsProviderCard,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "onboarding-provider-card",
|
|
componentPath: "./components/onboarding-provider-card.js",
|
|
component: DroidOnboardingProviderCard,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "onboarding-setup-help",
|
|
componentPath: "./components/onboarding-setup-help.js",
|
|
component: DroidOnboardingSetupHelp,
|
|
},
|
|
{
|
|
pluginId: "fusion-plugin-droid-runtime",
|
|
slotId: "post-onboarding-recommendation",
|
|
componentPath: "./components/post-onboarding-recommendation.js",
|
|
component: DroidPostOnboardingRecommendation,
|
|
},
|
|
];
|
|
|
|
export function resolvePluginSlotComponent(entry: PluginUiSlotEntry): ComponentType<PluginSlotComponentProps> | null {
|
|
const hit = REGISTRY.find(
|
|
(candidate) =>
|
|
candidate.pluginId === entry.pluginId
|
|
&& candidate.slotId === entry.slot.slotId
|
|
&& candidate.componentPath === entry.slot.componentPath,
|
|
);
|
|
|
|
return hit?.component ?? null;
|
|
}
|