diff --git a/dashboard/src/v2/MemoryPage.tsx b/dashboard/src/v2/MemoryPage.tsx index 914c3b575d..4a0a1eddb2 100644 --- a/dashboard/src/v2/MemoryPage.tsx +++ b/dashboard/src/v2/MemoryPage.tsx @@ -20,6 +20,7 @@ import type { SprintRecord, AgentPreset } from "./types.js"; import { PageContainer } from "./components/layout/PageContainer.js"; import { PageHeader } from "./components/layout/PageHeader.js"; import { MEMORY_CAMERA, focusCameraOnPoint, zoomCameraTowardPoint, type CameraState } from "./lib/memory-camera.js"; +import { MEMORY_CATEGORY_MESSAGE_KEYS, translateMemory, useMemoryI18n } from "./i18n/messages/memory.js"; /* ─── Types ──────────────────────────────────────────────────────────────── */ @@ -27,23 +28,13 @@ interface Pulse { edgeIdx: number; progress: number; speed: number } /* ─── Config ─────────────────────────────────────────────────────────────── */ -const CAT: Record = { - architecture: { label: "Architecture", hex: "#00E0A0", r: 0, g: 224, b: 160 }, - codebase: { label: "Codebase", hex: "#FFB800", r: 255, g: 184, b: 0 }, - context: { label: "Context", hex: "#8B5CF6", r: 139, g: 92, b: 246 }, - preferences: { label: "Preferences", hex: "#94A3B8", r: 148, g: 163, b: 184 }, - patterns: { label: "Patterns", hex: "#F59E0B", r: 245, g: 158, b: 11 }, - decision: { label: "Decision", hex: "#64748B", r: 100, g: 116, b: 139 }, - error: { label: "Error", hex: "#F43F5E", r: 244, g: 63, b: 94 }, - learning: { label: "Learning", hex: "#33FFB8", r: 51, g: 255, b: 184 }, +const CAT: Record = { + architecture: { hex: "#00E0A0", r: 0, g: 224, b: 160 }, codebase: { hex: "#FFB800", r: 255, g: 184, b: 0 }, + context: { hex: "#8B5CF6", r: 139, g: 92, b: 246 }, preferences: { hex: "#94A3B8", r: 148, g: 163, b: 184 }, + patterns: { hex: "#F59E0B", r: 245, g: 158, b: 11 }, decision: { hex: "#64748B", r: 100, g: 116, b: 139 }, + error: { hex: "#F43F5E", r: 244, g: 63, b: 94 }, learning: { hex: "#33FFB8", r: 51, g: 255, b: 184 }, }; -type MemTier = "short_term" | "long_term"; -const TIER_TABS: { key: MemTier; label: string; scope: MemoryScope }[] = [ - { key: "short_term", label: "Short Term", scope: "sprint" }, - { key: "long_term", label: "Long Term", scope: "project" }, -]; - const CATEGORIES: MemoryCategory[] = ["architecture", "codebase", "context", "preferences", "patterns", "decision", "error", "learning"]; const AMBIENT_LABEL_MIN_ZOOM = 0.9; const DEEP_LABEL_MIN_ZOOM = 2.35; @@ -221,6 +212,9 @@ function drawFocusedLabel( /* ─── Memory Page ────────────────────────────────────────────────────────── */ export const MemoryPage: FunctionComponent = () => { + const { formatNumber, locale, t, tp } = useMemoryI18n(); + const localeRef = useRef(locale); + localeRef.current = locale; const { selectedProject } = useProjectData(); const pid = selectedProject?.id || ""; const headerRef = useRef(null); @@ -325,10 +319,11 @@ export const MemoryPage: FunctionComponent = () => { const activeMemory = activeMemoryIdSignal.value ? graphNodes.find((node) => node.id === activeMemoryIdSignal.value && node.alive) : null; - const activeMemoryCategory = activeMemory ? (CAT[activeMemory.category] || CAT.context).label : null; + const activeMemoryCategory = activeMemory ? t(MEMORY_CATEGORY_MESSAGE_KEYS[activeMemory.category] ?? "categoryContext") : null; + const activeEntity = t(skillsActive ? "skillNoun" : "memoryNoun"); const selectionStatus = activeMemory - ? `Selected ${activeMemoryCategory} ${skillsActive ? "skill" : "memory"}: ${activeMemory.content}` - : `No ${skillsActive ? "skill" : "memory"} selected`; + ? t("selectedEntity", { category: activeMemoryCategory ?? "", entity: activeEntity, content: activeMemory.content }) + : t("noEntitySelected", { entity: activeEntity }); /* ── Fetch agent presets on project change ─────────────── */ useEffect(() => { @@ -552,7 +547,7 @@ export const MemoryPage: FunctionComponent = () => { ctx.fillStyle = lob ? `rgba(227,0,15,${dark ? 0.28 : 0.2})` : `rgba(${c.r},${c.g},${c.b},${dark ? 0.32 : 0.22})`; - ctx.fillText(c.label.toUpperCase(), centroid.x, centroid.y); + ctx.fillText(translateMemory(localeRef.current, MEMORY_CATEGORY_MESSAGE_KEYS[cat as MemoryCategory] ?? "categoryContext").toLocaleUpperCase(localeRef.current), centroid.x, centroid.y); } } @@ -715,7 +710,7 @@ export const MemoryPage: FunctionComponent = () => { drawFocusedLabel( ctx, n, - cc.label, + translateMemory(localeRef.current, MEMORY_CATEGORY_MESSAGE_KEYS[n.category] ?? "categoryContext"), dark, lob, cam.zoom, @@ -735,7 +730,7 @@ export const MemoryPage: FunctionComponent = () => { ctx.textAlign = "center"; ctx.font = `700 9px "JetBrains Mono", monospace`; ctx.fillStyle = lob ? "rgba(227,0,15,0.5)" : "rgba(0,224,160,0.5)"; - ctx.fillText(lob ? "LOBOTOMIZE" : "NEURAL CORE", scx, scy + 32 * cam.zoom); + ctx.fillText(translateMemory(localeRef.current, lob ? "lobotomizeCanvas" : "neuralCoreCanvas"), scx, scy + 32 * cam.zoom); scheduleDraw(); } @@ -885,10 +880,11 @@ export const MemoryPage: FunctionComponent = () => { return; } // Local text filter - const lower = q.toLowerCase(); + const lower = q.toLocaleLowerCase(localeRef.current); const matches = new Set(); s.graph.nodes.forEach((n, i) => { - if (n.alive && (n.content.toLowerCase().includes(lower) || n.category.includes(lower))) + const localizedCategory = translateMemory(localeRef.current, MEMORY_CATEGORY_MESSAGE_KEYS[n.category] ?? "categoryContext").toLocaleLowerCase(localeRef.current); + if (n.alive && (n.content.toLocaleLowerCase(localeRef.current).includes(lower) || n.category.includes(lower) || localizedCategory.includes(lower))) matches.add(i); }); s.searchMatch = matches; @@ -910,7 +906,7 @@ export const MemoryPage: FunctionComponent = () => { useEffect(() => { handleSearch(searchQuerySignal.value); - }, [graphData, handleSearch]); + }, [graphData, handleSearch, locale]); /* ── Lobotomize toggle ────────────────────────────────────────────── */ const handleLobotomizeToggle = useCallback(() => { @@ -975,7 +971,7 @@ export const MemoryPage: FunctionComponent = () => { /* ─── Render ──────────────────────────────────────────────────────── */ return ( - +
{ style={{ animation: "lobotomize-pulse 2s ease-in-out infinite" }}>

- Warning — Lobotomize mode active. - {" "}Single-click a graph node to delete it immediately. Inspector deletion is immediate; sidebar cards must be armed before deleting. + {t("warningLobotomize")} + {" "}{t("lobotomizeInstructions")}

)} @@ -1022,7 +1018,7 @@ export const MemoryPage: FunctionComponent = () => { className="flex flex-col lg:flex-row w-full overflow-hidden rounded-[2rem] bg-white/50 dark:bg-void-800/40 backdrop-blur-2xl border border-black/[0.05] dark:border-white/[0.05] shadow-[0_8px_48px_rgba(0,0,0,0.06)] dark:shadow-[0_8px_48px_rgba(0,0,0,0.4)] h-[calc(100dvh-12rem)] min-h-[500px]" >
- + {/* Zoom controls */}
{ }`} > {[ - { icon: ZoomIn, fn: zoomIn, title: "Zoom in" }, - { icon: ZoomOut, fn: zoomOut, title: "Zoom out" }, - { icon: Maximize2, fn: zoomReset, title: "Reset view" }, + { icon: ZoomIn, fn: zoomIn, title: t("zoomIn") }, + { icon: ZoomOut, fn: zoomOut, title: t("zoomOut") }, + { icon: Maximize2, fn: zoomReset, title: t("resetView") }, ].map(({ icon: Icon, fn, title }) => ( -
@@ -1081,12 +1077,12 @@ export const MemoryPage: FunctionComponent = () => { : "bottom-5 left-5" }`} > - {Object.entries(CAT).map(([, cfg]) => ( -
+ {Object.entries(CAT).map(([key, cfg]) => ( +
- {cfg.label} + {t(MEMORY_CATEGORY_MESSAGE_KEYS[key as MemoryCategory] ?? "categoryContext")}
))} @@ -1101,7 +1097,9 @@ export const MemoryPage: FunctionComponent = () => { }`} > - {memoryCount} {skillsActive ? "skill nodes" : "nodes"} + {skillsActive + ? t("entityNodes", { countLabel: formatNumber(memoryCount), entity: t("skillNoun") }) + : tp("node", memoryCount, { formattedCount: formatNumber(memoryCount) })} {selectionStatus}
@@ -1111,12 +1109,12 @@ export const MemoryPage: FunctionComponent = () => {

- {skillsActive ? "No skills indexed yet" : "No memories yet"} + {t(skillsActive ? "noSkillsIndexed" : "noMemoriesYet")}

{skillsActive - ? "Create or attach a persistent skill storage to visualize its catalog." - : "Memories will appear here as sprints capture them, or add one manually."} + ? t("emptySkillsMap") + : t("emptyMemoryMap")}

)} @@ -1131,8 +1129,8 @@ export const MemoryPage: FunctionComponent = () => {
)} @@ -1173,10 +1171,10 @@ export const MemoryPage: FunctionComponent = () => { shadow-[0_2px_12px_rgba(0,0,0,0.04)] dark:shadow-[0_2px_12px_rgba(0,0,0,0.2)]">
- {alive}/{total} + {formatNumber(alive)}/{formatNumber(total)}
- {cfg.label} + {t(MEMORY_CATEGORY_MESSAGE_KEYS[key as MemoryCategory] ?? "categoryContext")}
(null); const closeTimerRef = useRef(null); const interactionTokens = useInteractionTokens(); + const { formatNumber, t } = useMemoryI18n(); useEffect(() => { if (!open) { @@ -65,7 +67,7 @@ export const AddMemoryModal: FunctionComponent<{ event.preventDefault(); if (!content.trim()) { setShowError(true); - setFeedback({ status: "error", message: "Add a memory description before submitting." }); + setFeedback({ status: "error", message: t("addDescriptionBeforeSubmit") }); contentRef.current?.focus({ preventScroll: true }); return; } @@ -75,7 +77,7 @@ export const AddMemoryModal: FunctionComponent<{ await createMemory(projectId, { scope, content: content.trim(), category, strength }); setContent(""); setShowError(false); - setFeedback({ status: "success", message: "Memory added. Refreshing the workspace." }); + setFeedback({ status: "success", message: t("memoryAddedRefreshing") }); await onCreated(); closeTimerRef.current = window.setTimeout(() => { closeTimerRef.current = null; @@ -85,7 +87,7 @@ export const AddMemoryModal: FunctionComponent<{ } catch (error) { setFeedback({ status: "error", - message: error instanceof Error ? error.message : "Failed to add memory. Check the content and try again." + message: error instanceof Error ? error.message : t("addMemoryFallbackError") }); } finally { setSaving(false); @@ -112,8 +114,8 @@ export const AddMemoryModal: FunctionComponent<{ onClick={e => e.stopPropagation()} onSubmit={(event) => { void handleSubmit(event); }} role="dialog" aria-modal="true" aria-labelledby="add-memory-title" aria-describedby="add-memory-status" aria-busy={saving}> -

Add Memory

- +

{t("addMemoryTitle")}

+