diff --git a/understand-anything-plugin/packages/dashboard/src/App.tsx b/understand-anything-plugin/packages/dashboard/src/App.tsx index 5a3bf84..edbb7fc 100644 --- a/understand-anything-plugin/packages/dashboard/src/App.tsx +++ b/understand-anything-plugin/packages/dashboard/src/App.tsx @@ -23,7 +23,7 @@ import type { KeyboardShortcut } from "./hooks/useKeyboardShortcuts"; import { ThemeProvider } from "./themes/index.ts"; import { ThemePicker } from "./components/ThemePicker.tsx"; import type { ThemeConfig } from "./themes/index.ts"; -import { I18nProvider } from "./contexts/I18nContext.tsx"; +import { I18nProvider, useI18n } from "./contexts/I18nContext.tsx"; // Lazy-load heavy / optional components so they ship in separate chunks. const CodeViewer = lazy(() => import("./components/CodeViewer")); @@ -97,43 +97,13 @@ function App() { } function Dashboard({ accessToken }: { accessToken: string }) { - const graph = useDashboardStore((s) => s.graph); const setGraph = useDashboardStore((s) => s.setGraph); - const selectedNodeId = useDashboardStore((s) => s.selectedNodeId); - const tourActive = useDashboardStore((s) => s.tourActive); - const persona = useDashboardStore((s) => s.persona); - const codeViewerOpen = useDashboardStore((s) => s.codeViewerOpen); - const codeViewerExpanded = useDashboardStore((s) => s.codeViewerExpanded); - const expandCodeViewer = useDashboardStore((s) => s.expandCodeViewer); - const collapseCodeViewer = useDashboardStore((s) => s.collapseCodeViewer); + const setDomainGraph = useDashboardStore((s) => s.setDomainGraph); const setDiffOverlay = useDashboardStore((s) => s.setDiffOverlay); - const pathFinderOpen = useDashboardStore((s) => s.pathFinderOpen); - const togglePathFinder = useDashboardStore((s) => s.togglePathFinder); - const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters); - const toggleNodeTypeFilter = useDashboardStore((s) => s.toggleNodeTypeFilter); - const detailLevel = useDashboardStore((s) => s.detailLevel); - const setDetailLevel = useDashboardStore((s) => s.setDetailLevel); - const showFunctionsInClassView = useDashboardStore((s) => s.showFunctionsInClassView); - const toggleShowFunctionsInClassView = useDashboardStore((s) => s.toggleShowFunctionsInClassView); const [loadError, setLoadError] = useState(null); const [graphIssues, setGraphIssues] = useState([]); - const [showKeyboardHelp, setShowKeyboardHelp] = useState(false); const [metaTheme, setMetaTheme] = useState(null); - const [sidebarTab, setSidebarTab] = useState("info"); const [outputLanguage, setOutputLanguage] = useState(); - const viewMode = useDashboardStore((s) => s.viewMode); - const setViewMode = useDashboardStore((s) => s.setViewMode); - const isKnowledgeGraph = useDashboardStore((s) => s.isKnowledgeGraph); - const domainGraph = useDashboardStore((s) => s.domainGraph); - const setDomainGraph = useDashboardStore((s) => s.setDomainGraph); - const layoutIssues = useDashboardStore((s) => s.layoutIssues); - const isMobile = useIsMobile(); - // Schema issues + ELK layout issues share the WarningBanner — graph-load - // problems and dashboard rendering problems are equally surfaced. - const allIssues = useMemo( - () => [...graphIssues, ...layoutIssues], - [graphIssues, layoutIssues], - ); useEffect(() => { fetch(dataUrl("meta.json", accessToken)) @@ -150,128 +120,6 @@ function Dashboard({ accessToken }: { accessToken: string }) { .catch(() => {}); }, []); - useEffect(() => { - if (selectedNodeId) setSidebarTab("info"); - }, [selectedNodeId]); - - // Define keyboard shortcuts - const shortcuts = useMemo( - () => [ - // Help - { - key: "?", - shiftKey: true, - description: "Show keyboard shortcuts", - action: () => setShowKeyboardHelp((prev) => !prev), - category: "General", - }, - // Navigation - { - key: "Escape", - description: "Close panels and modals / go back to overview", - action: () => { - // Read from store at invocation time to avoid stale closures - const state = useDashboardStore.getState(); - if (state.pathFinderOpen) { - state.togglePathFinder(); - } else if (state.filterPanelOpen) { - state.toggleFilterPanel(); - } else if (state.exportMenuOpen) { - state.toggleExportMenu(); - } else if (state.codeViewerExpanded) { - state.collapseCodeViewer(); - } else if (state.codeViewerOpen) { - state.closeCodeViewer(); - } else if (state.selectedNodeId) { - state.selectNode(null); - } else if (state.navigationLevel === "layer-detail") { - state.navigateToOverview(); - } else if (state.tourActive) { - state.stopTour(); - } else { - setShowKeyboardHelp(false); - } - }, - category: "Navigation", - }, - { - key: "/", - description: "Focus search bar", - action: () => { - const searchInput = document.querySelector( - 'input[placeholder*="Search"]' - ); - searchInput?.focus(); - }, - category: "Navigation", - }, - // Tour controls - { - key: "ArrowRight", - description: "Next tour step", - action: () => { - const state = useDashboardStore.getState(); - if (state.tourActive) { - state.nextTourStep(); - } - }, - category: "Tour", - }, - { - key: "ArrowLeft", - description: "Previous tour step", - action: () => { - const state = useDashboardStore.getState(); - if (state.tourActive) { - state.prevTourStep(); - } - }, - category: "Tour", - }, - // View toggles - { - key: "d", - description: "Toggle diff mode", - action: () => { - const state = useDashboardStore.getState(); - state.toggleDiffMode(); - }, - category: "View", - }, - { - key: "f", - description: "Toggle filter panel", - action: () => { - const state = useDashboardStore.getState(); - state.toggleFilterPanel(); - }, - category: "View", - }, - { - key: "e", - description: "Toggle export menu", - action: () => { - const state = useDashboardStore.getState(); - state.toggleExportMenu(); - }, - category: "View", - }, - { - key: "p", - description: "Open path finder", - action: () => { - const state = useDashboardStore.getState(); - state.togglePathFinder(); - }, - category: "View", - }, - ], - [] - ); - - // Register keyboard shortcuts - useKeyboardShortcuts(shortcuts); - useEffect(() => { fetch(dataUrl("knowledge-graph.json", accessToken)) .then((res) => res.json()) @@ -280,9 +128,8 @@ function Dashboard({ accessToken }: { accessToken: string }) { if (result.success && result.data) { setGraph(result.data); setGraphIssues(result.issues); - // Auto-detect knowledge graph kind if ((data as Record).kind === "knowledge") { - setViewMode("knowledge"); + useDashboardStore.getState().setViewMode("knowledge"); useDashboardStore.getState().setIsKnowledgeGraph(true); } for (const issue of result.issues) { @@ -327,9 +174,7 @@ function Dashboard({ accessToken }: { accessToken: string }) { } } }) - .catch(() => { - // Silently ignore - diff overlay is optional - }); + .catch(() => {}); }, [setDiffOverlay]); useEffect(() => { @@ -347,11 +192,183 @@ function Dashboard({ accessToken }: { accessToken: string }) { console.warn(`[domain-graph] validation failed: ${result.fatal}`); } }) - .catch(() => { - // Silently ignore — domain graph is optional - }); + .catch(() => {}); }, [setDomainGraph]); + return ( + + + + + + ); +} + +function DashboardContent({ + accessToken, + loadError, + graphIssues, +}: { + accessToken: string; + loadError: string | null; + graphIssues: GraphIssue[]; +}) { + const graph = useDashboardStore((s) => s.graph); + const selectedNodeId = useDashboardStore((s) => s.selectedNodeId); + const tourActive = useDashboardStore((s) => s.tourActive); + const persona = useDashboardStore((s) => s.persona); + const codeViewerOpen = useDashboardStore((s) => s.codeViewerOpen); + const codeViewerExpanded = useDashboardStore((s) => s.codeViewerExpanded); + const expandCodeViewer = useDashboardStore((s) => s.expandCodeViewer); + const collapseCodeViewer = useDashboardStore((s) => s.collapseCodeViewer); + const pathFinderOpen = useDashboardStore((s) => s.pathFinderOpen); + const togglePathFinder = useDashboardStore((s) => s.togglePathFinder); + const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters); + const toggleNodeTypeFilter = useDashboardStore((s) => s.toggleNodeTypeFilter); + const detailLevel = useDashboardStore((s) => s.detailLevel); + const setDetailLevel = useDashboardStore((s) => s.setDetailLevel); + const showFunctionsInClassView = useDashboardStore((s) => s.showFunctionsInClassView); + const toggleShowFunctionsInClassView = useDashboardStore((s) => s.toggleShowFunctionsInClassView); + const [showKeyboardHelp, setShowKeyboardHelp] = useState(false); + const [sidebarTab, setSidebarTab] = useState("info"); + const viewMode = useDashboardStore((s) => s.viewMode); + const setViewMode = useDashboardStore((s) => s.setViewMode); + const isKnowledgeGraph = useDashboardStore((s) => s.isKnowledgeGraph); + const domainGraph = useDashboardStore((s) => s.domainGraph); + const layoutIssues = useDashboardStore((s) => s.layoutIssues); + const isMobile = useIsMobile(); + const { t } = useI18n(); + const allIssues = useMemo( + () => [...graphIssues, ...layoutIssues], + [graphIssues, layoutIssues], + ); + + useEffect(() => { + if (selectedNodeId) setSidebarTab("info"); + }, [selectedNodeId]); + + // Define keyboard shortcuts + const shortcuts = useMemo( + () => [ + // Help + { + key: "?", + shiftKey: true, + description: t.keyboardShortcuts.showHelp, + action: () => setShowKeyboardHelp((prev) => !prev), + category: "General", + }, + // Navigation + { + key: "Escape", + description: t.keyboardShortcuts.escapeDesc, + action: () => { + // Read from store at invocation time to avoid stale closures + const state = useDashboardStore.getState(); + if (state.pathFinderOpen) { + state.togglePathFinder(); + } else if (state.filterPanelOpen) { + state.toggleFilterPanel(); + } else if (state.exportMenuOpen) { + state.toggleExportMenu(); + } else if (state.codeViewerExpanded) { + state.collapseCodeViewer(); + } else if (state.codeViewerOpen) { + state.closeCodeViewer(); + } else if (state.selectedNodeId) { + state.selectNode(null); + } else if (state.navigationLevel === "layer-detail") { + state.navigateToOverview(); + } else if (state.tourActive) { + state.stopTour(); + } else { + setShowKeyboardHelp(false); + } + }, + category: "Navigation", + }, + { + key: "/", + description: t.keyboardShortcuts.focusSearch, + action: () => { + const searchInput = document.querySelector( + 'input[placeholder*="Search"]' + ); + searchInput?.focus(); + }, + category: "Navigation", + }, + // Tour controls + { + key: "ArrowRight", + description: t.keyboardShortcuts.nextStep, + action: () => { + const state = useDashboardStore.getState(); + if (state.tourActive) { + state.nextTourStep(); + } + }, + category: "Tour", + }, + { + key: "ArrowLeft", + description: t.keyboardShortcuts.prevStep, + action: () => { + const state = useDashboardStore.getState(); + if (state.tourActive) { + state.prevTourStep(); + } + }, + category: "Tour", + }, + // View toggles + { + key: "d", + description: t.keyboardShortcuts.toggleDiff, + action: () => { + const state = useDashboardStore.getState(); + state.toggleDiffMode(); + }, + category: "View", + }, + { + key: "f", + description: t.keyboardShortcuts.toggleFilter, + action: () => { + const state = useDashboardStore.getState(); + state.toggleFilterPanel(); + }, + category: "View", + }, + { + key: "e", + description: t.keyboardShortcuts.toggleExport, + action: () => { + const state = useDashboardStore.getState(); + state.toggleExportMenu(); + }, + category: "View", + }, + { + key: "p", + description: t.keyboardShortcuts.openPathFinder, + action: () => { + const state = useDashboardStore.getState(); + state.togglePathFinder(); + }, + category: "View", + }, + ], + [t] + ); + + // Register keyboard shortcuts + useKeyboardShortcuts(shortcuts); + // Determine sidebar content // NodeInfo always takes priority when a node is selected. // Learn mode adds LearnPanel below it; otherwise ProjectOverview shows when idle. @@ -382,7 +399,7 @@ function Dashboard({ accessToken }: { accessToken: string }) { : "text-text-muted hover:text-text-primary hover:bg-elevated" }`} > - {tab === "info" ? "Info" : "Files"} + {tab === "info" ? t.sidebar.info : t.sidebar.files} ))} @@ -394,31 +411,25 @@ function Dashboard({ accessToken }: { accessToken: string }) { if (isMobile) { return ( - - - - - + ); } return ( - -
{/* Header */}
{/* Left — fixed */}

- {graph?.project.name ?? "Understand Anything"} + {graph?.project.name ?? t.common.appName}

@@ -429,26 +440,26 @@ function Dashboard({ accessToken }: { accessToken: string }) {
@@ -467,55 +478,55 @@ function Dashboard({ accessToken }: { accessToken: string }) {
{detailLevel === "class" && ( )} )}
{(isKnowledgeGraph ? [ - { key: "knowledge" as const, label: "All", color: "var(--color-node-article)" }, + { key: "knowledge" as const, label: t.nodeTypeLabels.all, color: "var(--color-node-article)" }, ] : [ - { key: "code" as const, label: "Code", color: "var(--color-node-file)" }, - { key: "config" as const, label: "Config", color: "var(--color-node-config)" }, - { key: "docs" as const, label: "Docs", color: "var(--color-node-document)" }, - { key: "infra" as const, label: "Infra", color: "var(--color-node-service)" }, - { key: "data" as const, label: "Data", color: "var(--color-node-table)" }, - { key: "domain" as const, label: "Domain", color: "var(--color-node-concept)" }, - { key: "knowledge" as const, label: "Knowledge", color: "var(--color-node-article)" }, + { key: "code" as const, label: t.nodeTypeLabels.code, color: "var(--color-node-file)" }, + { key: "config" as const, label: t.nodeTypeLabels.config, color: "var(--color-node-config)" }, + { key: "docs" as const, label: t.nodeTypeLabels.docs, color: "var(--color-node-document)" }, + { key: "infra" as const, label: t.nodeTypeLabels.infra, color: "var(--color-node-service)" }, + { key: "data" as const, label: t.nodeTypeLabels.data, color: "var(--color-node-table)" }, + { key: "domain" as const, label: t.nodeTypeLabels.domain, color: "var(--color-node-concept)" }, + { key: "knowledge" as const, label: t.nodeTypeLabels.knowledge, color: "var(--color-node-article)" }, ]).map((cat) => (
@@ -673,8 +684,6 @@ function Dashboard({ accessToken }: { accessToken: string }) { )}
-
-
); } diff --git a/understand-anything-plugin/packages/dashboard/src/components/Breadcrumb.tsx b/understand-anything-plugin/packages/dashboard/src/components/Breadcrumb.tsx index 9bbb47b..bb00949 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/Breadcrumb.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/Breadcrumb.tsx @@ -1,10 +1,12 @@ import { useDashboardStore } from "../store"; +import { useI18n } from "../contexts/I18nContext"; export default function Breadcrumb() { const navigationLevel = useDashboardStore((s) => s.navigationLevel); const activeLayerId = useDashboardStore((s) => s.activeLayerId); const graph = useDashboardStore((s) => s.graph); const navigateToOverview = useDashboardStore((s) => s.navigateToOverview); + const { t } = useI18n(); const activeLayer = graph?.layers.find((l) => l.id === activeLayerId); @@ -12,7 +14,7 @@ export default function Breadcrumb() {
{navigationLevel === "overview" && (
- Project Overview + {t.breadcrumb.projectOverview}
)} @@ -22,14 +24,14 @@ export default function Breadcrumb() { onClick={navigateToOverview} className="text-gold hover:text-gold-bright transition-colors" > - Project + {t.breadcrumb.project} - {activeLayer?.name ?? "Layer"} + {activeLayer?.name ?? t.layer.defaultName} - (Esc to go back) + ({t.breadcrumb.escBack})
)} diff --git a/understand-anything-plugin/packages/dashboard/src/components/CodeViewer.tsx b/understand-anything-plugin/packages/dashboard/src/components/CodeViewer.tsx index 66ad67c..592fb1a 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/CodeViewer.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/CodeViewer.tsx @@ -1,6 +1,7 @@ import { useEffect, useMemo, useState } from "react"; import { Highlight, themes } from "prism-react-renderer"; import { useDashboardStore } from "../store"; +import { useI18n } from "../contexts/I18nContext"; interface CodeViewerProps { accessToken: string; @@ -78,6 +79,7 @@ export default function CodeViewer({ source: null, error: null, }); + const { t } = useI18n(); useEffect(() => { if (!node?.filePath) { @@ -125,7 +127,7 @@ export default function CodeViewer({ if (!node) { return (
-

No file selected

+

{t.codeViewer.noFile}

); } @@ -133,8 +135,8 @@ export default function CodeViewer({ const source = state.source; const language = source?.language ?? fallbackLanguage(node.filePath); const lineInfo = highlightedRange - ? `Lines ${highlightedRange.start}-${highlightedRange.end}` - : "Full file"; + ? `${t.codeViewer.lines} ${highlightedRange.start}-${highlightedRange.end}` + : t.codeViewer.fullFile; const isModal = presentation === "modal"; const handleClose = onClose ?? closeCodeViewer; @@ -170,8 +172,8 @@ export default function CodeViewer({ type="button" onClick={onExpand} className="text-text-muted hover:text-text-primary transition-colors" - title="Open larger code viewer" - aria-label="Open larger code viewer" + title={t.codeViewer.openLarger} + aria-label={t.codeViewer.openLarger} > @@ -182,8 +184,8 @@ export default function CodeViewer({ type="button" onClick={handleClose} className="text-text-muted hover:text-text-primary transition-colors" - title={isModal ? "Close expanded code viewer" : "Close code viewer"} - aria-label={isModal ? "Close expanded code viewer" : "Close code viewer"} + title={isModal ? t.codeViewer.closeExpanded : t.codeViewer.closeViewer} + aria-label={isModal ? t.codeViewer.closeExpanded : t.codeViewer.closeViewer} > @@ -194,13 +196,13 @@ export default function CodeViewer({
{state.status === "loading" && ( -
Loading source...
+
{t.codeViewer.loading}
)} {state.status === "error" && (
-
Source unavailable
+
{t.codeViewer.sourceUnavailable}

{state.error}

@@ -209,7 +211,7 @@ export default function CodeViewer({ {source && ( <>
- {source.lineCount} lines + {source.lineCount} {t.codeViewer.linesLabel} {formatBytes(source.sizeBytes)}
diff --git a/understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx b/understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx index bfc50b4..5dffd2d 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/CustomNode.tsx @@ -2,6 +2,7 @@ import { memo } from "react"; import { Handle, Position } from "@xyflow/react"; import type { NodeProps, Node } from "@xyflow/react"; import type { NodeType } from "@understand-anything/core/types"; +import { useI18n } from "../contexts/I18nContext"; // Color maps keyed by NodeType — must be kept in sync with core NodeType union. const typeColors: Record = { @@ -88,6 +89,7 @@ function CustomNodeComponent({ const barColor = typeColors[knownType] ?? typeColors.file; const textColor = typeTextColors[knownType] ?? typeTextColors.file; const complexityColor = complexityColors[data.complexity] ?? complexityColors.simple; + const { t } = useI18n(); if (import.meta.env.DEV && !(knownType in typeColors)) { console.warn(`[CustomNode] Unknown node type "${data.nodeType}" — using "file" colors`); @@ -159,8 +161,8 @@ function CustomNodeComponent({ )}
diff --git a/understand-anything-plugin/packages/dashboard/src/components/DiffToggle.tsx b/understand-anything-plugin/packages/dashboard/src/components/DiffToggle.tsx index f912d1a..a3dd2ff 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/DiffToggle.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/DiffToggle.tsx @@ -1,10 +1,12 @@ import { useDashboardStore } from "../store"; +import { useI18n } from "../contexts/I18nContext"; export default function DiffToggle() { const diffMode = useDashboardStore((s) => s.diffMode); const toggleDiffMode = useDashboardStore((s) => s.toggleDiffMode); const changedNodeIds = useDashboardStore((s) => s.changedNodeIds); const affectedNodeIds = useDashboardStore((s) => s.affectedNodeIds); + const { t } = useI18n(); const hasDiff = changedNodeIds.size > 0; @@ -23,9 +25,9 @@ export default function DiffToggle() { title={ hasDiff ? diffMode - ? "Hide diff overlay" - : "Show diff overlay" - : "No diff data loaded" + ? t.diffToggle.hideOverlay + : t.diffToggle.showOverlay + : t.diffToggle.noData } > Diff {diffMode && hasDiff ? "ON" : "OFF"} @@ -39,7 +41,7 @@ export default function DiffToggle() { style={{ backgroundColor: "var(--color-diff-changed)" }} /> - Changed + {t.diffToggle.changed} ({changedNodeIds.size}) @@ -51,7 +53,7 @@ export default function DiffToggle() { style={{ backgroundColor: "var(--color-diff-affected)" }} /> - Affected + {t.diffToggle.affected} ({affectedNodeIds.size}) diff --git a/understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx b/understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx index 6730000..ff15846 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/DomainGraphView.tsx @@ -17,6 +17,7 @@ import type { FlowFlowNode } from "./FlowNode"; import StepNode from "./StepNode"; import type { StepFlowNode } from "./StepNode"; import { useDashboardStore } from "../store"; +import { useI18n } from "../contexts/I18nContext"; import { mergeElkPositions, nodesToElkInput } from "../utils/layout"; import { applyElkLayout } from "../utils/elk-layout"; import type { KnowledgeGraph, GraphNode } from "@understand-anything/core/types"; @@ -167,6 +168,7 @@ function DomainGraphViewInner() { const domainGraph = useDashboardStore((s) => s.domainGraph); const activeDomainId = useDashboardStore((s) => s.activeDomainId); const clearActiveDomain = useDashboardStore((s) => s.clearActiveDomain); + const { t } = useI18n(); // Build structural nodes/edges/dims synchronously; only the layout call // itself is async, so we memo the structural pieces and run ELK in an @@ -237,7 +239,7 @@ function DomainGraphViewInner() { onClick={() => clearActiveDomain()} className="px-3 py-1.5 text-xs rounded-lg bg-elevated border border-border-subtle text-text-secondary hover:text-text-primary transition-colors" > - Back to domains + {t.domainView.backToDomains} )} diff --git a/understand-anything-plugin/packages/dashboard/src/components/ExportMenu.tsx b/understand-anything-plugin/packages/dashboard/src/components/ExportMenu.tsx index e3ed709..2a35fc9 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/ExportMenu.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/ExportMenu.tsx @@ -1,5 +1,6 @@ import { useEffect, useRef } from "react"; import { useDashboardStore } from "../store"; +import { useI18n } from "../contexts/I18nContext"; import type { KnowledgeGraph } from "@understand-anything/core/types"; import { filterNodes, filterEdges } from "../utils/filters"; @@ -26,6 +27,7 @@ export default function ExportMenu() { const toggleExportMenu = useDashboardStore((s) => s.toggleExportMenu); const reactFlowInstance = useDashboardStore((s) => s.reactFlowInstance); const persona = useDashboardStore((s) => s.persona); + const { t } = useI18n(); const containerRef = useRef(null); @@ -218,7 +220,7 @@ export default function ExportMenu() { {exportMenuOpen && ( @@ -247,7 +249,7 @@ export default function ExportMenu() { - Export as PNG + {t.export.asPNG} diff --git a/understand-anything-plugin/packages/dashboard/src/components/KeyboardShortcutsHelp.tsx b/understand-anything-plugin/packages/dashboard/src/components/KeyboardShortcutsHelp.tsx index b05c65e..87d0381 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/KeyboardShortcutsHelp.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/KeyboardShortcutsHelp.tsx @@ -1,5 +1,6 @@ import type { KeyboardShortcut } from "../hooks/useKeyboardShortcuts"; import { formatShortcutKey } from "../hooks/useKeyboardShortcuts"; +import { useI18n } from "../contexts/I18nContext"; interface KeyboardShortcutsHelpProps { shortcuts: KeyboardShortcut[]; @@ -10,6 +11,8 @@ export default function KeyboardShortcutsHelp({ shortcuts, onClose, }: KeyboardShortcutsHelpProps) { + const { t } = useI18n(); + // Group shortcuts by category const groupedShortcuts = shortcuts.reduce((acc, shortcut) => { if (!acc[shortcut.category]) { @@ -19,6 +22,14 @@ export default function KeyboardShortcutsHelp({ return acc; }, {} as Record); + // Translate category names + const categoryTranslations: Record = { + "General": t.keyboardShortcuts.general, + "Navigation": t.keyboardShortcuts.navigation, + "Tour": t.keyboardShortcuts.tour, + "View": t.keyboardShortcuts.view, + }; + return (

- Keyboard Shortcuts + {t.keyboardShortcuts.title}

- Press ? anytime to toggle this help + {t.keyboardShortcuts.toggleHint}

- Steps + {t.learnPanel.steps}

{tourSteps.map((step, i) => (

- Tour + {t.learnPanel.tour}

{currentTourStep + 1} / {totalSteps} @@ -97,7 +99,7 @@ export default function LearnPanel() { onClick={stopTour} className="text-[10px] text-text-muted hover:text-text-secondary transition-colors" > - Exit Tour + {t.learnPanel.exitTour}
@@ -213,13 +215,13 @@ export default function LearnPanel() { disabled={isFirst} className="flex-1 text-xs bg-elevated text-text-secondary py-1.5 rounded-lg hover:bg-surface disabled:opacity-40 disabled:cursor-not-allowed transition-colors" > - Prev + {t.learnPanel.prev}
diff --git a/understand-anything-plugin/packages/dashboard/src/components/MobileBottomNav.tsx b/understand-anything-plugin/packages/dashboard/src/components/MobileBottomNav.tsx index 44b42ef..83662ca 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/MobileBottomNav.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/MobileBottomNav.tsx @@ -1,4 +1,5 @@ import type { ReactNode } from "react"; +import { useI18n } from "../contexts/I18nContext"; export type MobileTab = "graph" | "info" | "files"; @@ -7,61 +8,58 @@ interface Props { onTabChange: (tab: MobileTab) => void; } -const tabs: { id: MobileTab; label: string; icon: ReactNode }[] = [ - { - id: "graph", - label: "Graph", - icon: ( - - - - - - - ), - }, - { - id: "info", - label: "Info", - icon: ( - - - - - ), - }, - { - id: "files", - label: "Files", - icon: ( - - - - ), - }, -]; +const tabIcons: Record = { + graph: ( + + + + + + + ), + info: ( + + + + + ), + files: ( + + + + ), +}; + +const tabOrder: MobileTab[] = ["graph", "info", "files"]; export default function MobileBottomNav({ activeTab, onTabChange }: Props) { + const { t } = useI18n(); + const labels: Record = { + graph: t.mobile.graph, + info: t.mobile.info, + files: t.mobile.files, + }; + return (
)}
- Diff overlay + {t.drawer.diffOverlay}
- Node types + {t.drawer.nodeTypes}
{filterDefs.map((cat) => { const active = nodeTypeFilters[cat.key] !== false; @@ -203,7 +207,7 @@ export default function MobileDrawer({ {graph && (graph.layers?.length ?? 0) > 0 && (
- Layers + {t.drawer.layers}
@@ -211,7 +215,7 @@ export default function MobileDrawer({ )}
- Tools + {t.drawer.tools}
@@ -231,7 +235,7 @@ export default function MobileDrawer({ d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" /> - Path + {t.drawer.path}
diff --git a/understand-anything-plugin/packages/dashboard/src/components/MobileLayout.tsx b/understand-anything-plugin/packages/dashboard/src/components/MobileLayout.tsx index 72d3d4e..27e4801 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/MobileLayout.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/MobileLayout.tsx @@ -1,6 +1,7 @@ import { lazy, Suspense, useEffect, useState } from "react"; import type { GraphIssue } from "@understand-anything/core/schema"; import { useDashboardStore } from "../store"; +import { useI18n } from "../contexts/I18nContext"; import GraphView from "./GraphView"; import DomainGraphView from "./DomainGraphView"; import KnowledgeGraphView from "./KnowledgeGraphView"; @@ -45,6 +46,7 @@ export default function MobileLayout({ const closeCodeViewer = useDashboardStore((s) => s.closeCodeViewer); const pathFinderOpen = useDashboardStore((s) => s.pathFinderOpen); const togglePathFinder = useDashboardStore((s) => s.togglePathFinder); + const { t } = useI18n(); const [activeTab, setActiveTab] = useState("graph"); const [drawerOpen, setDrawerOpen] = useState(false); @@ -96,7 +98,7 @@ export default function MobileLayout({

- {graph?.project.name ?? "Understand Anything"} + {graph?.project.name ?? t.common.appName}

{searchQuery.trim() && ( - {searchResults.length} result{searchResults.length !== 1 ? "s" : ""}{" "} + {searchResults.length} {t.search.result}{searchResults.length !== 1 ? "s" : ""}{" "} ({searchMode}) )} diff --git a/understand-anything-plugin/packages/dashboard/src/components/ThemePicker.tsx b/understand-anything-plugin/packages/dashboard/src/components/ThemePicker.tsx index 49eea73..8c7e9e7 100644 --- a/understand-anything-plugin/packages/dashboard/src/components/ThemePicker.tsx +++ b/understand-anything-plugin/packages/dashboard/src/components/ThemePicker.tsx @@ -1,11 +1,13 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { useTheme, PRESETS } from "../themes/index.ts"; import type { HeadingFont } from "../themes/index.ts"; +import { useI18n } from "../contexts/I18nContext"; export function ThemePicker() { const { config, preset, setPreset, setAccent, setHeadingFont } = useTheme(); const [open, setOpen] = useState(false); const ref = useRef(null); + const { t } = useI18n(); // Close on outside click useEffect(() => { @@ -41,7 +43,7 @@ export function ThemePicker() { {open && ( @@ -67,7 +69,7 @@ export function ThemePicker() { {/* Presets */}
- Theme + {t.themePicker.theme}
{PRESETS.map((p) => ( @@ -119,7 +121,7 @@ export function ThemePicker() { {/* Accent swatches */}
- Accent Color + {t.themePicker.accentColor}
{preset.accentSwatches.map((swatch) => ( @@ -141,13 +143,13 @@ export function ThemePicker() { {/* Heading font */}
- Heading Font + {t.themePicker.headingFont}
{([ - { id: "serif" as HeadingFont, label: "Serif", sample: "Aa" }, - { id: "sans" as HeadingFont, label: "Sans", sample: "Aa" }, - { id: "mono" as HeadingFont, label: "Mono", sample: "Aa" }, + { id: "serif" as HeadingFont, label: t.themePicker.serif, sample: "Aa" }, + { id: "sans" as HeadingFont, label: t.themePicker.sans, sample: "Aa" }, + { id: "mono" as HeadingFont, label: t.themePicker.mono, sample: "Aa" }, ]).map((opt) => (