Files
fusion/packages/dashboard/app/hooks/useFileEditor.ts
gsxdsm 1e49494bac feat(i18n): full-sweep string migration — 5,930 keys across 5 locales (#1352)
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>
2026-06-03 19:06:53 -07:00

126 lines
3.3 KiB
TypeScript

import { useState, useEffect, useCallback } from "react";
import { useTranslation } from "react-i18next";
import { getErrorMessage } from "@fusion/core";
import type { FileContentResponse, SaveFileResponse } from "../api";
import { fetchFileContent, saveFileContent } from "../api";
interface UseFileEditorReturn {
content: string;
setContent: (content: string) => void;
originalContent: string;
loading: boolean;
saving: boolean;
error: string | null;
save: () => Promise<void>;
hasChanges: boolean;
mtime: string | null;
}
/**
* Hook for editing a file in a task directory.
*
* @param taskId - The task ID
* @param filePath - The file path to edit (null if no file selected)
* @param enabled - Whether to enable loading (e.g., when editor is visible)
* @param projectId - Optional project ID for scoped store resolution
* @returns File editor state and controls
*/
export function useFileEditor(
taskId: string,
filePath: string | null,
enabled: boolean,
projectId?: string
): UseFileEditorReturn {
const { t } = useTranslation("app");
const [content, setContentState] = useState<string>("");
const [originalContent, setOriginalContent] = useState<string>("");
const [mtime, setMtime] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
const setContent = useCallback((newContent: string) => {
setContentState(newContent);
setError(null);
}, []);
// Load file content when filePath changes
useEffect(() => {
if (!enabled || !taskId || !filePath) {
setContentState("");
setOriginalContent("");
setMtime(null);
setError(null);
return;
}
let cancelled = false;
async function loadFile() {
setLoading(true);
setError(null);
try {
const response: FileContentResponse = await fetchFileContent(taskId, filePath!, projectId);
if (!cancelled) {
setContentState(response.content);
setOriginalContent(response.content);
setMtime(response.mtime);
}
} catch (err) {
if (!cancelled) {
setError(getErrorMessage(err) || t("editor.failedLoadFile", "Failed to load file"));
setContentState("");
setOriginalContent("");
setMtime(null);
}
} finally {
if (!cancelled) {
setLoading(false);
}
}
}
loadFile();
return () => {
cancelled = true;
};
}, [taskId, filePath, enabled, projectId]);
const hasChanges = content !== originalContent;
const save = useCallback(async () => {
if (!taskId || !filePath || !hasChanges) {
return;
}
setSaving(true);
setError(null);
try {
const response: SaveFileResponse = await saveFileContent(taskId, filePath, content, projectId);
setOriginalContent(content);
setMtime(response.mtime);
} catch (err) {
setError(getErrorMessage(err) || t("editor.failedSaveFile", "Failed to save file"));
throw err;
} finally {
setSaving(false);
}
}, [taskId, filePath, content, hasChanges, projectId]);
return {
content,
setContent,
originalContent,
loading,
saving,
error,
save,
hasChanges,
mtime,
};
}