mirror of
https://github.com/Egonex-AI/Understand-Anything.git
synced 2026-06-22 10:58:03 +08:00
feat(dashboard): Complete i18n translation for all UI components
This commit is contained in:
@@ -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<string | null>(null);
|
||||
const [graphIssues, setGraphIssues] = useState<GraphIssue[]>([]);
|
||||
const [showKeyboardHelp, setShowKeyboardHelp] = useState(false);
|
||||
const [metaTheme, setMetaTheme] = useState<ThemeConfig | null>(null);
|
||||
const [sidebarTab, setSidebarTab] = useState<SidebarTab>("info");
|
||||
const [outputLanguage, setOutputLanguage] = useState<string | undefined>();
|
||||
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<KeyboardShortcut[]>(
|
||||
() => [
|
||||
// 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<HTMLInputElement>(
|
||||
'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<string, unknown>).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 (
|
||||
<I18nProvider language={outputLanguage ?? "en"}>
|
||||
<ThemeProvider metaTheme={metaTheme}>
|
||||
<DashboardContent
|
||||
accessToken={accessToken}
|
||||
loadError={loadError}
|
||||
graphIssues={graphIssues}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
||||
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<SidebarTab>("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<KeyboardShortcut[]>(
|
||||
() => [
|
||||
// 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<HTMLInputElement>(
|
||||
'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}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
@@ -394,31 +411,25 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<I18nProvider language={outputLanguage ?? "en"}>
|
||||
<ThemeProvider metaTheme={metaTheme}>
|
||||
<MobileLayout
|
||||
accessToken={accessToken}
|
||||
showKeyboardHelp={showKeyboardHelp}
|
||||
setShowKeyboardHelp={setShowKeyboardHelp}
|
||||
loadError={loadError}
|
||||
allIssues={allIssues}
|
||||
shortcuts={shortcuts}
|
||||
/>
|
||||
</ThemeProvider>
|
||||
</I18nProvider>
|
||||
<MobileLayout
|
||||
accessToken={accessToken}
|
||||
showKeyboardHelp={showKeyboardHelp}
|
||||
setShowKeyboardHelp={setShowKeyboardHelp}
|
||||
loadError={loadError}
|
||||
allIssues={allIssues}
|
||||
shortcuts={shortcuts}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<I18nProvider language={outputLanguage ?? "en"}>
|
||||
<ThemeProvider metaTheme={metaTheme}>
|
||||
<div className="h-screen w-screen flex flex-col bg-root text-text-primary noise-overlay">
|
||||
{/* Header */}
|
||||
<header className="flex items-center px-3 sm:px-5 py-3 bg-surface border-b border-border-subtle shrink-0 gap-2 sm:gap-4">
|
||||
{/* Left — fixed */}
|
||||
<div className="flex items-center gap-3 sm:gap-5 shrink-0 min-w-0">
|
||||
<h1 className="font-heading text-base sm:text-lg text-text-primary tracking-wide truncate max-w-[160px] sm:max-w-[220px] lg:max-w-none">
|
||||
{graph?.project.name ?? "Understand Anything"}
|
||||
{graph?.project.name ?? t.common.appName}
|
||||
</h1>
|
||||
<div className="w-px h-5 bg-border-subtle hidden sm:block" />
|
||||
<PersonaSelector />
|
||||
@@ -429,26 +440,26 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewMode("domain")}
|
||||
title="Switch to domain view"
|
||||
title={t.drawer.domain}
|
||||
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
||||
viewMode === "domain"
|
||||
? "bg-accent/20 text-accent"
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
Domain
|
||||
{t.drawer.domain}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setViewMode("structural")}
|
||||
title="Switch to structural view"
|
||||
title={t.drawer.structural}
|
||||
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
||||
viewMode === "structural"
|
||||
? "bg-accent/20 text-accent"
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
Structural
|
||||
{t.drawer.structural}
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
@@ -467,55 +478,55 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDetailLevel("file")}
|
||||
title="Files only — architecture-level dependencies (fast)"
|
||||
title={t.detailLevel.filesTitle}
|
||||
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
||||
detailLevel === "file"
|
||||
? "bg-accent/20 text-accent"
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
Files
|
||||
{t.detailLevel.files}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setDetailLevel("class")}
|
||||
title="Files + Classes — code structure with inheritance"
|
||||
title={t.detailLevel.classesTitle}
|
||||
className={`px-3 py-1 text-xs font-medium rounded-md transition-colors ${
|
||||
detailLevel === "class"
|
||||
? "bg-accent/20 text-accent"
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
+Classes
|
||||
{t.detailLevel.classes}
|
||||
</button>
|
||||
</div>
|
||||
{detailLevel === "class" && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleShowFunctionsInClassView}
|
||||
title="Toggle function nodes (may slow down rendering)"
|
||||
title={t.detailLevel.fnTitle}
|
||||
className={`text-[10px] font-semibold uppercase tracking-wider px-2 py-1 rounded border transition-colors ${
|
||||
showFunctionsInClassView
|
||||
? "border-amber-500/50 bg-amber-500/10 text-amber-400"
|
||||
: "border-border-medium bg-elevated text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
fn
|
||||
{t.detailLevel.fn}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="flex items-center gap-1">
|
||||
{(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) => (
|
||||
<button
|
||||
key={cat.key}
|
||||
@@ -549,7 +560,7 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
<button
|
||||
onClick={togglePathFinder}
|
||||
className="flex items-center gap-1.5 px-2 sm:px-3 py-1.5 rounded-lg text-sm bg-elevated text-text-secondary hover:text-text-primary transition-colors"
|
||||
title="Find path between nodes (P)"
|
||||
title={t.pathFinder.title}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
@@ -564,13 +575,13 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
|
||||
/>
|
||||
</svg>
|
||||
<span className="hidden md:inline">Path</span>
|
||||
<span className="hidden md:inline">{t.common.path}</span>
|
||||
</button>
|
||||
<ThemePicker />
|
||||
<button
|
||||
onClick={() => setShowKeyboardHelp(true)}
|
||||
className="text-text-muted hover:text-accent transition-colors"
|
||||
title="Keyboard shortcuts (Shift + ?)"
|
||||
title={t.keyboardShortcuts.showHelp}
|
||||
>
|
||||
<svg
|
||||
className="w-5 h-5"
|
||||
@@ -616,7 +627,7 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
<GraphView />
|
||||
)}
|
||||
<div className="absolute top-3 right-3 text-sm text-text-muted/60 pointer-events-none select-none">
|
||||
Press <kbd className="kbd">?</kbd> for keyboard shortcuts
|
||||
{t.common.pressKeyboard}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -673,8 +684,6 @@ function Dashboard({ accessToken }: { accessToken: string }) {
|
||||
</Suspense>
|
||||
)}
|
||||
</div>
|
||||
</ThemeProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
<div className="absolute top-4 left-4 z-10 flex items-center gap-2">
|
||||
{navigationLevel === "overview" && (
|
||||
<div className="px-4 py-2 rounded-full bg-elevated border border-border-subtle text-xs font-semibold tracking-wider uppercase text-text-secondary shadow-lg">
|
||||
Project Overview
|
||||
{t.breadcrumb.projectOverview}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -22,14 +24,14 @@ export default function Breadcrumb() {
|
||||
onClick={navigateToOverview}
|
||||
className="text-gold hover:text-gold-bright transition-colors"
|
||||
>
|
||||
Project
|
||||
{t.breadcrumb.project}
|
||||
</button>
|
||||
<span className="text-text-muted">›</span>
|
||||
<span className="text-text-primary">
|
||||
{activeLayer?.name ?? "Layer"}
|
||||
{activeLayer?.name ?? t.layer.defaultName}
|
||||
</span>
|
||||
<span className="text-text-muted ml-1 text-[10px] normal-case tracking-normal">
|
||||
(Esc to go back)
|
||||
({t.breadcrumb.escBack})
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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 (
|
||||
<div className="h-full w-full flex items-center justify-center bg-surface">
|
||||
<p className="text-text-muted text-sm">No file selected</p>
|
||||
<p className="text-text-muted text-sm">{t.codeViewer.noFile}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 9V4h5M20 15v5h-5M4 4l6 6M20 20l-6-6" />
|
||||
@@ -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}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
||||
@@ -194,13 +196,13 @@ export default function CodeViewer({
|
||||
|
||||
<div className="flex-1 min-h-0 overflow-auto bg-root">
|
||||
{state.status === "loading" && (
|
||||
<div className="p-5 text-sm text-text-muted">Loading source...</div>
|
||||
<div className="p-5 text-sm text-text-muted">{t.codeViewer.loading}</div>
|
||||
)}
|
||||
|
||||
{state.status === "error" && (
|
||||
<div className="p-5">
|
||||
<div className="rounded-lg border border-border-subtle bg-elevated p-4">
|
||||
<div className="text-sm font-medium text-text-primary mb-2">Source unavailable</div>
|
||||
<div className="text-sm font-medium text-text-primary mb-2">{t.codeViewer.sourceUnavailable}</div>
|
||||
<p className="text-sm text-text-secondary leading-relaxed">{state.error}</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -209,7 +211,7 @@ export default function CodeViewer({
|
||||
{source && (
|
||||
<>
|
||||
<div className="px-4 py-2 border-b border-border-subtle bg-surface text-[11px] text-text-muted flex items-center justify-between">
|
||||
<span>{source.lineCount} lines</span>
|
||||
<span>{source.lineCount} {t.codeViewer.linesLabel}</span>
|
||||
<span>{formatBytes(source.sizeBytes)}</span>
|
||||
</div>
|
||||
<Highlight code={source.content} language={language} theme={themes.vsDark}>
|
||||
|
||||
@@ -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<NodeType, string> = {
|
||||
@@ -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({
|
||||
<span
|
||||
className="inline-block w-1.5 h-1.5 rounded-full bg-node-function shadow-[0_0_4px_rgba(90,158,111,0.6)]"
|
||||
role="img"
|
||||
aria-label="Tested"
|
||||
title="Has tests"
|
||||
aria-label={t.customNode.tested}
|
||||
title={t.customNode.hasTests}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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)" }}
|
||||
/>
|
||||
<span className="text-text-secondary text-[11px]">
|
||||
Changed
|
||||
{t.diffToggle.changed}
|
||||
<span className="text-text-muted ml-0.5">
|
||||
({changedNodeIds.size})
|
||||
</span>
|
||||
@@ -51,7 +53,7 @@ export default function DiffToggle() {
|
||||
style={{ backgroundColor: "var(--color-diff-affected)" }}
|
||||
/>
|
||||
<span className="text-text-secondary text-[11px]">
|
||||
Affected
|
||||
{t.diffToggle.affected}
|
||||
<span className="text-text-muted ml-0.5">
|
||||
({affectedNodeIds.size})
|
||||
</span>
|
||||
|
||||
@@ -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}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
|
||||
@@ -218,7 +220,7 @@ export default function ExportMenu() {
|
||||
<button
|
||||
onClick={toggleExportMenu}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm bg-elevated text-text-secondary hover:text-text-primary transition-colors"
|
||||
title="Export graph (E)"
|
||||
title={t.export.title}
|
||||
>
|
||||
<svg
|
||||
className="w-4 h-4"
|
||||
@@ -233,7 +235,7 @@ export default function ExportMenu() {
|
||||
d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4"
|
||||
/>
|
||||
</svg>
|
||||
Export
|
||||
{t.export.label}
|
||||
</button>
|
||||
|
||||
{exportMenuOpen && (
|
||||
@@ -247,7 +249,7 @@ export default function ExportMenu() {
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>Export as PNG</span>
|
||||
<span>{t.export.asPNG}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={exportSVG}
|
||||
@@ -257,7 +259,7 @@ export default function ExportMenu() {
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
||||
</svg>
|
||||
<span>Export as SVG</span>
|
||||
<span>{t.export.asSVG}</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={exportJSON}
|
||||
@@ -267,7 +269,7 @@ export default function ExportMenu() {
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
|
||||
</svg>
|
||||
<span>Export as JSON</span>
|
||||
<span>{t.export.asJSON}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+15
-4
@@ -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<string, KeyboardShortcut[]>);
|
||||
|
||||
// Translate category names
|
||||
const categoryTranslations: Record<string, string> = {
|
||||
"General": t.keyboardShortcuts.general,
|
||||
"Navigation": t.keyboardShortcuts.navigation,
|
||||
"Tour": t.keyboardShortcuts.tour,
|
||||
"View": t.keyboardShortcuts.view,
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50"
|
||||
@@ -32,10 +43,10 @@ export default function KeyboardShortcutsHelp({
|
||||
<div className="sticky top-0 glass-heavy border-b border-border-subtle px-6 py-4 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="text-xl font-heading text-text-primary">
|
||||
Keyboard Shortcuts
|
||||
{t.keyboardShortcuts.title}
|
||||
</h2>
|
||||
<p className="text-xs text-text-muted mt-1">
|
||||
Press <kbd className="kbd">?</kbd> anytime to toggle this help
|
||||
{t.keyboardShortcuts.toggleHint}
|
||||
</p>
|
||||
</div>
|
||||
<button
|
||||
@@ -63,7 +74,7 @@ export default function KeyboardShortcutsHelp({
|
||||
{Object.entries(groupedShortcuts).map(([category, categoryShortcuts]) => (
|
||||
<div key={category}>
|
||||
<h3 className="text-sm font-semibold text-accent uppercase tracking-wider mb-3">
|
||||
{category}
|
||||
{categoryTranslations[category] ?? category}
|
||||
</h3>
|
||||
<div className="space-y-2">
|
||||
{categoryShortcuts.map((shortcut, index) => (
|
||||
@@ -85,7 +96,7 @@ export default function KeyboardShortcutsHelp({
|
||||
{/* Footer */}
|
||||
<div className="sticky bottom-0 glass-heavy border-t border-border-subtle px-6 py-3 text-center">
|
||||
<p className="text-xs text-text-muted">
|
||||
Press <kbd className="kbd">ESC</kbd> to close
|
||||
{t.keyboardShortcuts.closeHint}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useDashboardStore } from "../store";
|
||||
import { useI18n } from "../contexts/I18nContext";
|
||||
|
||||
// Shared layer color palette — used by LayerLegend, LayerClusterNode, PortalNode, and GraphView
|
||||
export const LAYER_PALETTE = [
|
||||
@@ -19,6 +20,7 @@ export default function LayerLegend() {
|
||||
const graph = useDashboardStore((s) => s.graph);
|
||||
const navigationLevel = useDashboardStore((s) => s.navigationLevel);
|
||||
const activeLayerId = useDashboardStore((s) => s.activeLayerId);
|
||||
const { t } = useI18n();
|
||||
|
||||
const layers = graph?.layers ?? [];
|
||||
const hasLayers = layers.length > 0;
|
||||
@@ -31,8 +33,8 @@ export default function LayerLegend() {
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[11px] font-medium text-text-secondary whitespace-nowrap">
|
||||
{navigationLevel === "overview"
|
||||
? `${layers.length} layers`
|
||||
: activeLayer?.name ?? "Layer"}
|
||||
? `${layers.length} ${t.layer.label}`
|
||||
: activeLayer?.name ?? t.layer.defaultName}
|
||||
</span>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useMemo } from "react";
|
||||
import ReactMarkdown from "react-markdown";
|
||||
import { useDashboardStore } from "../store";
|
||||
import { useI18n } from "../contexts/I18nContext";
|
||||
|
||||
export default function LearnPanel() {
|
||||
const graph = useDashboardStore((s) => s.graph);
|
||||
@@ -12,6 +13,7 @@ export default function LearnPanel() {
|
||||
const nextTourStep = useDashboardStore((s) => s.nextTourStep);
|
||||
const prevTourStep = useDashboardStore((s) => s.prevTourStep);
|
||||
const selectNode = useDashboardStore((s) => s.selectNode);
|
||||
const { t } = useI18n();
|
||||
|
||||
const tourSteps = useMemo(
|
||||
() => graph?.tour ? [...graph.tour].sort((a, b) => a.order - b.order) : [],
|
||||
@@ -25,9 +27,9 @@ export default function LearnPanel() {
|
||||
<div className="h-full w-full flex items-center justify-center">
|
||||
<div className="text-center px-4">
|
||||
<div className="text-2xl mb-2 text-text-muted">🧭</div>
|
||||
<p className="text-text-muted text-sm">No tour available</p>
|
||||
<p className="text-text-muted text-sm">{t.learnPanel.noTour}</p>
|
||||
<p className="text-text-muted text-xs mt-1">
|
||||
Generate a tour from your knowledge graph to get a guided walkthrough
|
||||
{t.learnPanel.noTourHint}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,9 +41,9 @@ export default function LearnPanel() {
|
||||
return (
|
||||
<div className="h-full w-full overflow-auto p-5">
|
||||
<div className="mb-4">
|
||||
<h2 className="text-lg font-heading text-text-primary mb-1">Project Tour</h2>
|
||||
<h2 className="text-lg font-heading text-text-primary mb-1">{t.learnPanel.projectTour}</h2>
|
||||
<p className="text-xs text-text-muted">
|
||||
{tourSteps.length} steps · Guided walkthrough of the codebase
|
||||
{tourSteps.length} {t.learnPanel.steps} · {t.learnPanel.guidedWalkthrough}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -49,12 +51,12 @@ export default function LearnPanel() {
|
||||
onClick={startTour}
|
||||
className="w-full mb-4 bg-accent/10 border border-accent/30 text-accent text-sm font-medium py-2.5 px-4 rounded-lg hover:bg-accent/20 transition-colors"
|
||||
>
|
||||
Start Tour
|
||||
{t.learnPanel.startTour}
|
||||
</button>
|
||||
|
||||
<div className="space-y-2">
|
||||
<h3 className="text-[11px] font-semibold text-accent uppercase tracking-wider mb-2">
|
||||
Steps
|
||||
{t.learnPanel.steps}
|
||||
</h3>
|
||||
{tourSteps.map((step, i) => (
|
||||
<div
|
||||
@@ -87,7 +89,7 @@ export default function LearnPanel() {
|
||||
<div className="flex items-center justify-between px-3 py-2 border-b border-border-subtle shrink-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h3 className="text-[11px] font-semibold text-accent uppercase tracking-wider">
|
||||
Tour
|
||||
{t.learnPanel.tour}
|
||||
</h3>
|
||||
<span className="text-xs text-text-muted">
|
||||
{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}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -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}
|
||||
</button>
|
||||
<button
|
||||
onClick={isLast ? stopTour : nextTourStep}
|
||||
className="flex-1 text-xs bg-accent/10 border border-accent/30 text-accent py-1.5 rounded-lg hover:bg-accent/20 transition-colors"
|
||||
>
|
||||
{isLast ? "Finish" : "Next"}
|
||||
{isLast ? t.learnPanel.finish : t.learnPanel.next}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.6}>
|
||||
<circle cx="6" cy="7" r="2" />
|
||||
<circle cx="18" cy="7" r="2" />
|
||||
<circle cx="12" cy="17" r="2" />
|
||||
<path strokeLinecap="round" d="M7.6 8.5L11 15.5M16.4 8.5L13 15.5M8 7h8" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "info",
|
||||
label: "Info",
|
||||
icon: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.6}>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 11v5M12 8h.01" />
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: "files",
|
||||
label: "Files",
|
||||
icon: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.6}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4 6.5A1.5 1.5 0 0 1 5.5 5h3.382a1.5 1.5 0 0 1 1.342.83l.671 1.34A1.5 1.5 0 0 0 12.236 8H18.5A1.5 1.5 0 0 1 20 9.5v8a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 17.5z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
},
|
||||
];
|
||||
const tabIcons: Record<MobileTab, ReactNode> = {
|
||||
graph: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.6}>
|
||||
<circle cx="6" cy="7" r="2" />
|
||||
<circle cx="18" cy="7" r="2" />
|
||||
<circle cx="12" cy="17" r="2" />
|
||||
<path strokeLinecap="round" d="M7.6 8.5L11 15.5M16.4 8.5L13 15.5M8 7h8" />
|
||||
</svg>
|
||||
),
|
||||
info: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.6}>
|
||||
<circle cx="12" cy="12" r="9" />
|
||||
<path strokeLinecap="round" strokeLinejoin="round" d="M12 11v5M12 8h.01" />
|
||||
</svg>
|
||||
),
|
||||
files: (
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.6}>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M4 6.5A1.5 1.5 0 0 1 5.5 5h3.382a1.5 1.5 0 0 1 1.342.83l.671 1.34A1.5 1.5 0 0 0 12.236 8H18.5A1.5 1.5 0 0 1 20 9.5v8a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 4 17.5z"
|
||||
/>
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
const tabOrder: MobileTab[] = ["graph", "info", "files"];
|
||||
|
||||
export default function MobileBottomNav({ activeTab, onTabChange }: Props) {
|
||||
const { t } = useI18n();
|
||||
const labels: Record<MobileTab, string> = {
|
||||
graph: t.mobile.graph,
|
||||
info: t.mobile.info,
|
||||
files: t.mobile.files,
|
||||
};
|
||||
|
||||
return (
|
||||
<nav className="flex shrink-0 bg-surface border-t border-border-subtle">
|
||||
{tabs.map((tab) => {
|
||||
const active = activeTab === tab.id;
|
||||
{tabOrder.map((id) => {
|
||||
const active = activeTab === id;
|
||||
return (
|
||||
<button
|
||||
key={tab.id}
|
||||
key={id}
|
||||
type="button"
|
||||
onClick={() => onTabChange(tab.id)}
|
||||
onClick={() => onTabChange(id)}
|
||||
className={`relative flex-1 flex flex-col items-center justify-center gap-1 py-2.5 text-[10px] font-semibold uppercase tracking-[0.14em] transition-colors ${
|
||||
active ? "text-accent" : "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
aria-current={active ? "page" : undefined}
|
||||
>
|
||||
<span className="w-5 h-5">{tab.icon}</span>
|
||||
{tab.label}
|
||||
<span className="w-5 h-5">{tabIcons[id]}</span>
|
||||
{labels[id]}
|
||||
{active && (
|
||||
<span className="absolute top-0 left-1/2 -translate-x-1/2 w-8 h-px bg-accent" />
|
||||
)}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useEffect } from "react";
|
||||
import { useDashboardStore } from "../store";
|
||||
import { useI18n } from "../contexts/I18nContext";
|
||||
import PersonaSelector from "./PersonaSelector";
|
||||
import DiffToggle from "./DiffToggle";
|
||||
import LayerLegend from "./LayerLegend";
|
||||
@@ -20,19 +21,7 @@ interface NodeTypeFilterDef {
|
||||
color: string;
|
||||
}
|
||||
|
||||
const STRUCTURAL_FILTERS: NodeTypeFilterDef[] = [
|
||||
{ key: "code", label: "Code", color: "var(--color-node-file)" },
|
||||
{ key: "config", label: "Config", color: "var(--color-node-config)" },
|
||||
{ key: "docs", label: "Docs", color: "var(--color-node-document)" },
|
||||
{ key: "infra", label: "Infra", color: "var(--color-node-service)" },
|
||||
{ key: "data", label: "Data", color: "var(--color-node-table)" },
|
||||
{ key: "domain", label: "Domain", color: "var(--color-node-concept)" },
|
||||
{ key: "knowledge", label: "Knowledge", color: "var(--color-node-article)" },
|
||||
];
|
||||
|
||||
const KNOWLEDGE_FILTERS: NodeTypeFilterDef[] = [
|
||||
{ key: "knowledge", label: "All", color: "var(--color-node-article)" },
|
||||
];
|
||||
|
||||
function SectionLabel({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
@@ -55,6 +44,21 @@ export default function MobileDrawer({
|
||||
const setViewMode = useDashboardStore((s) => s.setViewMode);
|
||||
const nodeTypeFilters = useDashboardStore((s) => s.nodeTypeFilters);
|
||||
const toggleNodeTypeFilter = useDashboardStore((s) => s.toggleNodeTypeFilter);
|
||||
const { t } = useI18n();
|
||||
|
||||
const structuralFilters: NodeTypeFilterDef[] = [
|
||||
{ key: "code", label: t.nodeTypeLabels.code, color: "var(--color-node-file)" },
|
||||
{ key: "config", label: t.nodeTypeLabels.config, color: "var(--color-node-config)" },
|
||||
{ key: "docs", label: t.nodeTypeLabels.docs, color: "var(--color-node-document)" },
|
||||
{ key: "infra", label: t.nodeTypeLabels.infra, color: "var(--color-node-service)" },
|
||||
{ key: "data", label: t.nodeTypeLabels.data, color: "var(--color-node-table)" },
|
||||
{ key: "domain", label: t.nodeTypeLabels.domain, color: "var(--color-node-concept)" },
|
||||
{ key: "knowledge", label: t.nodeTypeLabels.knowledge, color: "var(--color-node-article)" },
|
||||
];
|
||||
|
||||
const knowledgeFilters: NodeTypeFilterDef[] = [
|
||||
{ key: "knowledge", label: t.nodeTypeLabels.all, color: "var(--color-node-article)" },
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
@@ -75,7 +79,7 @@ export default function MobileDrawer({
|
||||
};
|
||||
}, [open]);
|
||||
|
||||
const filterDefs = isKnowledgeGraph ? KNOWLEDGE_FILTERS : STRUCTURAL_FILTERS;
|
||||
const filterDefs = isKnowledgeGraph ? knowledgeFilters : structuralFilters;
|
||||
const showViewToggle = Boolean(graph && !isKnowledgeGraph && domainGraph);
|
||||
|
||||
return (
|
||||
@@ -105,10 +109,10 @@ export default function MobileDrawer({
|
||||
<header className="flex items-center justify-between px-5 py-4 border-b border-border-subtle shrink-0">
|
||||
<div>
|
||||
<span className="text-[10px] font-semibold uppercase tracking-[0.2em] text-accent">
|
||||
Controls
|
||||
{t.drawer.controls}
|
||||
</span>
|
||||
<h2 className="font-heading text-lg text-text-primary mt-0.5 leading-none">
|
||||
{graph?.project.name ?? "Dashboard"}
|
||||
{graph?.project.name ?? t.drawer.dashboard}
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
@@ -132,13 +136,13 @@ export default function MobileDrawer({
|
||||
{/* Body */}
|
||||
<div className="flex-1 overflow-auto px-5 py-5 space-y-7">
|
||||
<section>
|
||||
<SectionLabel>Role</SectionLabel>
|
||||
<SectionLabel>{t.drawer.role}</SectionLabel>
|
||||
<PersonaSelector />
|
||||
</section>
|
||||
|
||||
{showViewToggle && (
|
||||
<section>
|
||||
<SectionLabel>View</SectionLabel>
|
||||
<SectionLabel>{t.drawer.view}</SectionLabel>
|
||||
<div className="inline-flex items-center bg-elevated rounded-lg p-0.5">
|
||||
<button
|
||||
type="button"
|
||||
@@ -149,7 +153,7 @@ export default function MobileDrawer({
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
Domain
|
||||
{t.drawer.domain}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@@ -160,19 +164,19 @@ export default function MobileDrawer({
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
Structural
|
||||
{t.drawer.structural}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section>
|
||||
<SectionLabel>Diff overlay</SectionLabel>
|
||||
<SectionLabel>{t.drawer.diffOverlay}</SectionLabel>
|
||||
<DiffToggle />
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<SectionLabel>Node types</SectionLabel>
|
||||
<SectionLabel>{t.drawer.nodeTypes}</SectionLabel>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{filterDefs.map((cat) => {
|
||||
const active = nodeTypeFilters[cat.key] !== false;
|
||||
@@ -203,7 +207,7 @@ export default function MobileDrawer({
|
||||
|
||||
{graph && (graph.layers?.length ?? 0) > 0 && (
|
||||
<section>
|
||||
<SectionLabel>Layers</SectionLabel>
|
||||
<SectionLabel>{t.drawer.layers}</SectionLabel>
|
||||
<div className="-mx-1">
|
||||
<LayerLegend />
|
||||
</div>
|
||||
@@ -211,7 +215,7 @@ export default function MobileDrawer({
|
||||
)}
|
||||
|
||||
<section>
|
||||
<SectionLabel>Tools</SectionLabel>
|
||||
<SectionLabel>{t.drawer.tools}</SectionLabel>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<FilterPanel />
|
||||
<ExportMenu />
|
||||
@@ -231,7 +235,7 @@ export default function MobileDrawer({
|
||||
d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6"
|
||||
/>
|
||||
</svg>
|
||||
Path
|
||||
{t.drawer.path}
|
||||
</button>
|
||||
<ThemePicker />
|
||||
<button
|
||||
@@ -241,7 +245,7 @@ export default function MobileDrawer({
|
||||
onClose();
|
||||
}}
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-sm bg-elevated text-text-secondary hover:text-text-primary transition-colors"
|
||||
aria-label="Keyboard shortcuts"
|
||||
aria-label={t.drawer.help}
|
||||
>
|
||||
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
@@ -251,7 +255,7 @@ export default function MobileDrawer({
|
||||
d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||
/>
|
||||
</svg>
|
||||
Help
|
||||
{t.drawer.help}
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -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<MobileTab>("graph");
|
||||
const [drawerOpen, setDrawerOpen] = useState(false);
|
||||
@@ -96,7 +98,7 @@ export default function MobileLayout({
|
||||
</button>
|
||||
|
||||
<h1 className="font-heading text-base flex-1 min-w-0 truncate text-center text-text-primary tracking-wide">
|
||||
{graph?.project.name ?? "Understand Anything"}
|
||||
{graph?.project.name ?? t.common.appName}
|
||||
</h1>
|
||||
|
||||
<button
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useDashboardStore } from "../store";
|
||||
import { useI18n } from "../contexts/I18nContext";
|
||||
|
||||
const typeBadgeColors: Record<string, string> = {
|
||||
file: "text-node-file border border-node-file/30 bg-node-file/10",
|
||||
@@ -28,6 +29,7 @@ export default function SearchBar() {
|
||||
const navigateToNodeInLayer = useDashboardStore((s) => s.navigateToNodeInLayer);
|
||||
const searchMode = useDashboardStore((s) => s.searchMode);
|
||||
const setSearchMode = useDashboardStore((s) => s.setSearchMode);
|
||||
const { t } = useI18n();
|
||||
|
||||
const [dropdownOpen, setDropdownOpen] = useState(false);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
@@ -104,7 +106,7 @@ export default function SearchBar() {
|
||||
value={searchQuery}
|
||||
onChange={handleInputChange}
|
||||
onFocus={() => setDropdownOpen(true)}
|
||||
placeholder="Search nodes by name, summary, or tags..."
|
||||
placeholder={t.search.placeholder}
|
||||
className="flex-1 min-w-0 bg-elevated text-text-primary text-sm rounded-lg px-3 py-1.5 border border-border-subtle focus:outline-none focus:border-accent/50 placeholder-text-muted"
|
||||
/>
|
||||
<div className="flex items-center gap-1 bg-elevated rounded-lg p-0.5 shrink-0">
|
||||
@@ -116,7 +118,7 @@ export default function SearchBar() {
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
Fuzzy
|
||||
{t.search.fuzzy}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setSearchMode("semantic")}
|
||||
@@ -126,12 +128,12 @@ export default function SearchBar() {
|
||||
: "text-text-muted hover:text-text-secondary"
|
||||
}`}
|
||||
>
|
||||
Semantic
|
||||
{t.search.semantic}
|
||||
</button>
|
||||
</div>
|
||||
{searchQuery.trim() && (
|
||||
<span className="hidden sm:inline text-xs text-text-muted shrink-0">
|
||||
{searchResults.length} result{searchResults.length !== 1 ? "s" : ""}{" "}
|
||||
{searchResults.length} {t.search.result}{searchResults.length !== 1 ? "s" : ""}{" "}
|
||||
<span className="text-text-muted">({searchMode})</span>
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -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<HTMLDivElement>(null);
|
||||
const { t } = useI18n();
|
||||
|
||||
// Close on outside click
|
||||
useEffect(() => {
|
||||
@@ -41,7 +43,7 @@ export function ThemePicker() {
|
||||
<button
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className="flex items-center gap-1.5 px-2 py-1 rounded text-xs text-text-secondary hover:text-text-primary transition-colors"
|
||||
title="Change theme"
|
||||
title={t.themePicker.changeTheme}
|
||||
>
|
||||
<svg
|
||||
width="14"
|
||||
@@ -59,7 +61,7 @@ export function ThemePicker() {
|
||||
<circle cx="12" cy="7" r="1.5" fill="currentColor" />
|
||||
<circle cx="16" cy="10" r="1.5" fill="currentColor" />
|
||||
</svg>
|
||||
<span className="hidden sm:inline">Theme</span>
|
||||
<span className="hidden sm:inline">{t.common.theme}</span>
|
||||
</button>
|
||||
|
||||
{open && (
|
||||
@@ -67,7 +69,7 @@ export function ThemePicker() {
|
||||
{/* Presets */}
|
||||
<div>
|
||||
<div className="text-[10px] font-semibold text-text-muted uppercase tracking-wider mb-2">
|
||||
Theme
|
||||
{t.themePicker.theme}
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{PRESETS.map((p) => (
|
||||
@@ -119,7 +121,7 @@ export function ThemePicker() {
|
||||
{/* Accent swatches */}
|
||||
<div>
|
||||
<div className="text-[10px] font-semibold text-text-muted uppercase tracking-wider mb-2">
|
||||
Accent Color
|
||||
{t.themePicker.accentColor}
|
||||
</div>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{preset.accentSwatches.map((swatch) => (
|
||||
@@ -141,13 +143,13 @@ export function ThemePicker() {
|
||||
{/* Heading font */}
|
||||
<div>
|
||||
<div className="text-[10px] font-semibold text-text-muted uppercase tracking-wider mb-2">
|
||||
Heading Font
|
||||
{t.themePicker.headingFont}
|
||||
</div>
|
||||
<div className="flex gap-1">
|
||||
{([
|
||||
{ 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) => (
|
||||
<button
|
||||
key={opt.id}
|
||||
|
||||
@@ -17,6 +17,10 @@ export const en = {
|
||||
truncated: "(truncated)",
|
||||
preview: "Preview",
|
||||
doubleClickToOpen: "double-click to open",
|
||||
appName: "Understand Anything",
|
||||
pressKeyboard: "Press ? for keyboard shortcuts",
|
||||
path: "Path",
|
||||
theme: "Theme",
|
||||
},
|
||||
projectOverview: {
|
||||
nodes: "Nodes",
|
||||
@@ -30,6 +34,7 @@ export const en = {
|
||||
infra: "Infra",
|
||||
data: "Data",
|
||||
domain: "Domain",
|
||||
knowledge: "Knowledge",
|
||||
languages: "Languages",
|
||||
frameworks: "Frameworks",
|
||||
nodeTypeDistribution: "Node Type Distribution",
|
||||
@@ -73,6 +78,155 @@ export const en = {
|
||||
deepDive: "Deep Dive",
|
||||
deepDiveDesc: "Code-focused with chat",
|
||||
},
|
||||
sidebar: {
|
||||
info: "Info",
|
||||
files: "Files",
|
||||
},
|
||||
mobile: {
|
||||
graph: "Graph",
|
||||
info: "Info",
|
||||
files: "Files",
|
||||
},
|
||||
drawer: {
|
||||
controls: "Controls",
|
||||
dashboard: "Dashboard",
|
||||
role: "Role",
|
||||
view: "View",
|
||||
diffOverlay: "Diff overlay",
|
||||
nodeTypes: "Node types",
|
||||
layers: "Layers",
|
||||
tools: "Tools",
|
||||
path: "Path",
|
||||
help: "Help",
|
||||
structural: "Structural",
|
||||
domain: "Domain",
|
||||
},
|
||||
domainView: {
|
||||
backToDomains: "Back to domains",
|
||||
},
|
||||
detailLevel: {
|
||||
filesTitle: "Files only — architecture-level dependencies (fast)",
|
||||
classesTitle: "Files + Classes — code structure with inheritance",
|
||||
files: "Files",
|
||||
classes: "+Classes",
|
||||
fnTitle: "Toggle function nodes (may slow down rendering)",
|
||||
fn: "fn",
|
||||
},
|
||||
nodeTypeLabels: {
|
||||
all: "All",
|
||||
code: "Code",
|
||||
config: "Config",
|
||||
docs: "Docs",
|
||||
infra: "Infra",
|
||||
data: "Data",
|
||||
domain: "Domain",
|
||||
knowledge: "Knowledge",
|
||||
},
|
||||
tokenGate: {
|
||||
validating: "Validating...",
|
||||
continue: "Continue",
|
||||
},
|
||||
diffToggle: {
|
||||
hideOverlay: "Hide diff overlay",
|
||||
showOverlay: "Show diff overlay",
|
||||
noData: "No diff data loaded",
|
||||
changed: "Changed",
|
||||
affected: "Affected",
|
||||
},
|
||||
learnPanel: {
|
||||
finish: "Finish",
|
||||
next: "Next",
|
||||
prev: "Prev",
|
||||
noTour: "No tour available",
|
||||
noTourHint: "Generate a tour from your knowledge graph to get a guided walkthrough",
|
||||
projectTour: "Project Tour",
|
||||
steps: "steps",
|
||||
stepsTitle: "Steps",
|
||||
guidedWalkthrough: "Guided walkthrough of the codebase",
|
||||
startTour: "Start Tour",
|
||||
tour: "Tour",
|
||||
exitTour: "Exit Tour",
|
||||
},
|
||||
layer: {
|
||||
defaultName: "Layer",
|
||||
label: "layers",
|
||||
},
|
||||
breadcrumb: {
|
||||
projectOverview: "Project Overview",
|
||||
project: "Project",
|
||||
escBack: "Esc to go back",
|
||||
},
|
||||
warningBanner: {
|
||||
dropped: "Dropped",
|
||||
fatal: "Fatal",
|
||||
},
|
||||
themePicker: {
|
||||
changeTheme: "Change theme",
|
||||
theme: "Theme",
|
||||
accentColor: "Accent Color",
|
||||
headingFont: "Heading Font",
|
||||
serif: "Serif",
|
||||
sans: "Sans",
|
||||
mono: "Mono",
|
||||
},
|
||||
codeViewer: {
|
||||
fullFile: "Full file",
|
||||
lines: "Lines",
|
||||
linesLabel: "lines",
|
||||
noFile: "No file selected",
|
||||
loading: "Loading source...",
|
||||
openLarger: "Open larger code viewer",
|
||||
closeExpanded: "Close expanded code viewer",
|
||||
closeViewer: "Close code viewer",
|
||||
sourceUnavailable: "Source unavailable",
|
||||
},
|
||||
customNode: {
|
||||
tested: "Tested",
|
||||
hasTests: "Has tests",
|
||||
},
|
||||
ariaLabels: {
|
||||
openMenu: "Open menu",
|
||||
closeMenu: "Close menu",
|
||||
settings: "Settings",
|
||||
hideSearch: "Hide search",
|
||||
showSearch: "Show search",
|
||||
},
|
||||
nodeTypeFilter: {
|
||||
hide: "Hide",
|
||||
show: "Show",
|
||||
nodesLabel: "nodes",
|
||||
},
|
||||
keyboardShortcuts: {
|
||||
showHelp: "Show keyboard shortcuts",
|
||||
general: "General",
|
||||
navigation: "Navigation",
|
||||
tour: "Tour",
|
||||
view: "View",
|
||||
focusSearch: "Focus search bar",
|
||||
nextStep: "Next tour step",
|
||||
prevStep: "Previous tour step",
|
||||
toggleDiff: "Toggle diff mode",
|
||||
toggleFilter: "Toggle filter panel",
|
||||
toggleExport: "Toggle export menu",
|
||||
openPathFinder: "Open path finder",
|
||||
title: "Keyboard Shortcuts",
|
||||
toggleHint: "Press ? anytime to toggle this help",
|
||||
closeHint: "Press ESC to close",
|
||||
escapeDesc: "Close panels and modals / go back to overview",
|
||||
},
|
||||
search: {
|
||||
placeholder: "Search nodes by name, summary, or tags...",
|
||||
fuzzy: "Fuzzy",
|
||||
semantic: "Semantic",
|
||||
result: "result",
|
||||
},
|
||||
export: {
|
||||
label: "Export",
|
||||
title: "Export graph (E)",
|
||||
asPNG: "Export as PNG",
|
||||
asSVG: "Export as SVG",
|
||||
asJSON: "Export as JSON",
|
||||
},
|
||||
edgeLabels: {
|
||||
imports: { forward: "imports", backward: "imported by" },
|
||||
exports: { forward: "exports to", backward: "exported by" },
|
||||
@@ -110,6 +264,9 @@ export const en = {
|
||||
categorized_under: { forward: "categorized under", backward: "categorizes" },
|
||||
authored_by: { forward: "authored by", backward: "authored" },
|
||||
},
|
||||
pathFinder: {
|
||||
title: "Find path between nodes (P)",
|
||||
},
|
||||
};
|
||||
|
||||
export default en;
|
||||
@@ -17,6 +17,10 @@ export const ja = {
|
||||
truncated: "(省略)",
|
||||
preview: "プレビュー",
|
||||
doubleClickToOpen: "ダブルクリックで開く",
|
||||
appName: "Understand Anything",
|
||||
pressKeyboard: "? を押してキーボードショートカットを表示",
|
||||
path: "パス",
|
||||
theme: "テーマ",
|
||||
},
|
||||
projectOverview: {
|
||||
nodes: "ノード",
|
||||
@@ -30,6 +34,7 @@ export const ja = {
|
||||
infra: "インフラ",
|
||||
data: "データ",
|
||||
domain: "ドメイン",
|
||||
knowledge: "ナレッジ",
|
||||
languages: "プログラミング言語",
|
||||
frameworks: "フレームワーク",
|
||||
nodeTypeDistribution: "ノードタイプ分布",
|
||||
@@ -73,6 +78,155 @@ export const ja = {
|
||||
deepDive: "詳細",
|
||||
deepDiveDesc: "コード中心のチャット",
|
||||
},
|
||||
sidebar: {
|
||||
info: "情報",
|
||||
files: "ファイル",
|
||||
},
|
||||
mobile: {
|
||||
graph: "グラフ",
|
||||
info: "情報",
|
||||
files: "ファイル",
|
||||
},
|
||||
drawer: {
|
||||
controls: "コントロール",
|
||||
dashboard: "ダッシュボード",
|
||||
role: "ロール",
|
||||
view: "ビュー",
|
||||
diffOverlay: "差分オーバーレイ",
|
||||
nodeTypes: "ノードタイプ",
|
||||
layers: "レイヤー",
|
||||
tools: "ツール",
|
||||
path: "パス",
|
||||
help: "ヘルプ",
|
||||
structural: "構造",
|
||||
domain: "ドメイン",
|
||||
},
|
||||
domainView: {
|
||||
backToDomains: "ドメインに戻る",
|
||||
},
|
||||
detailLevel: {
|
||||
filesTitle: "ファイルのみ — アーキテクチャレベルの依存関係(高速)",
|
||||
classesTitle: "ファイル + クラス — 継承を含むコード構造",
|
||||
files: "ファイル",
|
||||
classes: "+クラス",
|
||||
fnTitle: "関数ノードを切り替え(レンダリングが遅くなる可能性)",
|
||||
fn: "fn",
|
||||
},
|
||||
nodeTypeLabels: {
|
||||
all: "すべて",
|
||||
code: "コード",
|
||||
config: "設定",
|
||||
docs: "ドキュメント",
|
||||
infra: "インフラ",
|
||||
data: "データ",
|
||||
domain: "ドメイン",
|
||||
knowledge: "ナレッジ",
|
||||
},
|
||||
tokenGate: {
|
||||
validating: "検証中...",
|
||||
continue: "続行",
|
||||
},
|
||||
diffToggle: {
|
||||
hideOverlay: "差分オーバーレイを非表示",
|
||||
showOverlay: "差分オーバーレイを表示",
|
||||
noData: "差分データが読み込まれていません",
|
||||
changed: "変更済み",
|
||||
affected: "影響あり",
|
||||
},
|
||||
learnPanel: {
|
||||
finish: "完了",
|
||||
next: "次へ",
|
||||
prev: "前へ",
|
||||
noTour: "ツアーがありません",
|
||||
noTourHint: "知識グラフからツアーを生成してコードベースのガイド付きウォークスルーを取得",
|
||||
projectTour: "プロジェクトツアー",
|
||||
steps: "ステップ",
|
||||
stepsTitle: "ステップ",
|
||||
guidedWalkthrough: "コードベースのガイド付きウォークスルー",
|
||||
startTour: "ツアー開始",
|
||||
tour: "ツアー",
|
||||
exitTour: "ツアー終了",
|
||||
},
|
||||
layer: {
|
||||
defaultName: "レイヤー",
|
||||
label: "レイヤー",
|
||||
},
|
||||
breadcrumb: {
|
||||
projectOverview: "プロジェクト概要",
|
||||
project: "プロジェクト",
|
||||
escBack: "Escで戻る",
|
||||
},
|
||||
warningBanner: {
|
||||
dropped: "削除済み",
|
||||
fatal: "致命的",
|
||||
},
|
||||
themePicker: {
|
||||
changeTheme: "テーマ変更",
|
||||
theme: "テーマ",
|
||||
accentColor: "アクセント色",
|
||||
headingFont: "見出しフォント",
|
||||
serif: "セリフ",
|
||||
sans: "サン",
|
||||
mono: "モノ",
|
||||
},
|
||||
codeViewer: {
|
||||
fullFile: "ファイル全体",
|
||||
lines: "行",
|
||||
linesLabel: "行",
|
||||
noFile: "ファイル未選択",
|
||||
loading: "ソース読み込み中...",
|
||||
openLarger: "大きなコードビューアを開く",
|
||||
closeExpanded: "展開したコードビューアを閉じる",
|
||||
closeViewer: "コードビューアを閉じる",
|
||||
sourceUnavailable: "ソースが利用できません",
|
||||
},
|
||||
customNode: {
|
||||
tested: "テスト済み",
|
||||
hasTests: "テストあり",
|
||||
},
|
||||
ariaLabels: {
|
||||
openMenu: "メニューを開く",
|
||||
closeMenu: "メニューを閉じる",
|
||||
settings: "設定",
|
||||
hideSearch: "検索を非表示",
|
||||
showSearch: "検索を表示",
|
||||
},
|
||||
nodeTypeFilter: {
|
||||
hide: "非表示",
|
||||
show: "表示",
|
||||
nodesLabel: "ノード",
|
||||
},
|
||||
keyboardShortcuts: {
|
||||
showHelp: "キーボードショートカットを表示",
|
||||
general: "一般",
|
||||
navigation: "ナビゲーション",
|
||||
tour: "ツアー",
|
||||
view: "ビュー",
|
||||
focusSearch: "検索バーにフォーカス",
|
||||
nextStep: "次のツアーステップ",
|
||||
prevStep: "前のツアーステップ",
|
||||
toggleDiff: "差分モード切り替え",
|
||||
toggleFilter: "フィルターパネル切り替え",
|
||||
toggleExport: "エクスポートメニュー切り替え",
|
||||
openPathFinder: "パスファインダーを開く",
|
||||
title: "キーボードショートカット",
|
||||
toggleHint: "いつでも ? を押してこのヘルプを切り替え",
|
||||
closeHint: "ESC を押して閉じる",
|
||||
escapeDesc: "パネルとモーダルを閉じる / 概要に戻る",
|
||||
},
|
||||
search: {
|
||||
placeholder: "ノード名、概要、タグで検索...",
|
||||
fuzzy: "ファジー",
|
||||
semantic: "セマンティック",
|
||||
result: "結果",
|
||||
},
|
||||
export: {
|
||||
label: "エクスポート",
|
||||
title: "グラフをエクスポート (E)",
|
||||
asPNG: "PNGでエクスポート",
|
||||
asSVG: "SVGでエクスポート",
|
||||
asJSON: "JSONでエクスポート",
|
||||
},
|
||||
edgeLabels: {
|
||||
imports: { forward: "インポート", backward: "インポートされる" },
|
||||
exports: { forward: "エクスポート", backward: "エクスポートされる" },
|
||||
@@ -110,6 +264,9 @@ export const ja = {
|
||||
categorized_under: { forward: "カテゴリ化", backward: "カテゴリ化する" },
|
||||
authored_by: { forward: "作成者", backward: "作成" },
|
||||
},
|
||||
pathFinder: {
|
||||
title: "ノード間のパスを検索 (P)",
|
||||
},
|
||||
};
|
||||
|
||||
export default ja;
|
||||
@@ -17,6 +17,10 @@ export const ko = {
|
||||
truncated: "(생략)",
|
||||
preview: "미리보기",
|
||||
doubleClickToOpen: "두 번 클릭하여 열기",
|
||||
appName: "Understand Anything",
|
||||
pressKeyboard: "? 키를 눌러 키보드 단축키 보기",
|
||||
path: "경로",
|
||||
theme: "테마",
|
||||
},
|
||||
projectOverview: {
|
||||
nodes: "노드",
|
||||
@@ -30,6 +34,7 @@ export const ko = {
|
||||
infra: "인프라",
|
||||
data: "데이터",
|
||||
domain: "도메인",
|
||||
knowledge: "지식",
|
||||
languages: "프로그래밍 언어",
|
||||
frameworks: "프레임워크",
|
||||
nodeTypeDistribution: "노드 타입 분포",
|
||||
@@ -73,7 +78,156 @@ export const ko = {
|
||||
deepDive: "심층",
|
||||
deepDiveDesc: "코드 중심 채팅",
|
||||
},
|
||||
edgeLabels: {
|
||||
sidebar: {
|
||||
info: "정보",
|
||||
files: "파일",
|
||||
},
|
||||
mobile: {
|
||||
graph: "그래프",
|
||||
info: "정보",
|
||||
files: "파일",
|
||||
},
|
||||
drawer: {
|
||||
controls: "컨트롤",
|
||||
dashboard: "대시보드",
|
||||
role: "역할",
|
||||
view: "보기",
|
||||
diffOverlay: "차분 오버레이",
|
||||
nodeTypes: "노드 타입",
|
||||
layers: "레이어",
|
||||
tools: "도구",
|
||||
path: "경로",
|
||||
help: "도움말",
|
||||
structural: "구조",
|
||||
domain: "도메인",
|
||||
},
|
||||
domainView: {
|
||||
backToDomains: "도메인으로 돌아가기",
|
||||
},
|
||||
detailLevel: {
|
||||
filesTitle: "파일만 — 아키텍처 레벨 의존성 (빠름)",
|
||||
classesTitle: "파일 + 클래스 — 상속 포함 코드 구조",
|
||||
files: "파일",
|
||||
classes: "+클래스",
|
||||
fnTitle: "함수 노드 토글 (렌더링 속도 저하 가능)",
|
||||
fn: "fn",
|
||||
},
|
||||
nodeTypeLabels: {
|
||||
all: "모두",
|
||||
code: "코드",
|
||||
config: "설정",
|
||||
docs: "문서",
|
||||
infra: "인프라",
|
||||
data: "데이터",
|
||||
domain: "도메인",
|
||||
knowledge: "지식",
|
||||
},
|
||||
tokenGate: {
|
||||
validating: "검증 중...",
|
||||
continue: "계속",
|
||||
},
|
||||
diffToggle: {
|
||||
hideOverlay: "차분 오버레이 숨기기",
|
||||
showOverlay: "차분 오버레이 표시",
|
||||
noData: "차분 데이터가 로드되지 않음",
|
||||
changed: "변경됨",
|
||||
affected: "영향받음",
|
||||
},
|
||||
learnPanel: {
|
||||
finish: "완료",
|
||||
next: "다음",
|
||||
prev: "이전",
|
||||
noTour: "투어 없음",
|
||||
noTourHint: "지식 그래프에서 투어를 생성하여 코드베이스의 가이드 워크스루를 얻으세요",
|
||||
projectTour: "프로젝트 투어",
|
||||
steps: "단계",
|
||||
stepsTitle: "단계",
|
||||
guidedWalkthrough: "코드베이스 가이드 워크스루",
|
||||
startTour: "투어 시작",
|
||||
tour: "투어",
|
||||
exitTour: "투어 종료",
|
||||
},
|
||||
layer: {
|
||||
defaultName: "레이어",
|
||||
label: "레이어",
|
||||
},
|
||||
breadcrumb: {
|
||||
projectOverview: "프로젝트 개요",
|
||||
project: "프로젝트",
|
||||
escBack: "Esc로 돌아가기",
|
||||
},
|
||||
warningBanner: {
|
||||
dropped: "삭제됨",
|
||||
fatal: "치명적",
|
||||
},
|
||||
themePicker: {
|
||||
changeTheme: "테마 변경",
|
||||
theme: "테마",
|
||||
accentColor: "강조색",
|
||||
headingFont: "제목 폰트",
|
||||
serif: "세리프",
|
||||
sans: "산스",
|
||||
mono: "모노",
|
||||
},
|
||||
codeViewer: {
|
||||
fullFile: "전체 파일",
|
||||
lines: "행",
|
||||
linesLabel: "행",
|
||||
noFile: "파일 선택 안 됨",
|
||||
loading: "소스 로딩 중...",
|
||||
openLarger: "더 큰 코드 뷰어 열기",
|
||||
closeExpanded: "확장된 코드 뷰어 닫기",
|
||||
closeViewer: "코드 뷰어 닫기",
|
||||
sourceUnavailable: "소스 사용 불가",
|
||||
},
|
||||
customNode: {
|
||||
tested: "테스트됨",
|
||||
hasTests: "테스트 있음",
|
||||
},
|
||||
ariaLabels: {
|
||||
openMenu: "메뉴 열기",
|
||||
closeMenu: "메뉴 닫기",
|
||||
settings: "설정",
|
||||
hideSearch: "검색 숨기기",
|
||||
showSearch: "검색 표시",
|
||||
},
|
||||
nodeTypeFilter: {
|
||||
hide: "숨기기",
|
||||
show: "표시",
|
||||
nodesLabel: "노드",
|
||||
},
|
||||
keyboardShortcuts: {
|
||||
showHelp: "키보드 단축키 표시",
|
||||
general: "일반",
|
||||
navigation: "탐색",
|
||||
tour: "투어",
|
||||
view: "보기",
|
||||
focusSearch: "검색창 포커스",
|
||||
nextStep: "다음 투어 단계",
|
||||
prevStep: "이전 투어 단계",
|
||||
toggleDiff: "차분 모드 전환",
|
||||
toggleFilter: "필터 패널 전환",
|
||||
toggleExport: "내보내기 메뉴 전환",
|
||||
openPathFinder: "경로 찾기 열기",
|
||||
title: "키보드 단축키",
|
||||
toggleHint: "언제든 ?를 눌러 이 도움말을 토글",
|
||||
closeHint: "ESC를 눌러 닫기",
|
||||
escapeDesc: "패널 및 모달 닫기 / 개요로 돌아가기",
|
||||
},
|
||||
search: {
|
||||
placeholder: "노드 이름, 요약, 태그로 검색...",
|
||||
fuzzy: "퍼지",
|
||||
semantic: "시맨틱",
|
||||
result: "결과",
|
||||
},
|
||||
export: {
|
||||
label: "내보내기",
|
||||
title: "그래프 내보내기 (E)",
|
||||
asPNG: "PNG로 내보내기",
|
||||
asSVG: "SVG로 내보내기",
|
||||
asJSON: "JSON으로 내보내기",
|
||||
},
|
||||
edgeLabels: {
|
||||
imports: { forward: "임포트", backward: "임포트됨" },
|
||||
exports: { forward: "내보내기", backward: "내보내기됨" },
|
||||
contains: { forward: "포함", backward: "포함됨" },
|
||||
@@ -98,7 +252,7 @@ export const ko = {
|
||||
documents: { forward: "문서화", backward: "문서화됨" },
|
||||
provisions: { forward: "제공", backward: "제공됨" },
|
||||
routes: { forward: "라우팅", backward: "라우팅됨" },
|
||||
defines_schema: { forward: "스키마 정의", backward: "스키마 정義됨" },
|
||||
defines_schema: { forward: "스키마 정의", backward: "스키마 정의됨" },
|
||||
triggers: { forward: "트리거", backward: "트리거됨" },
|
||||
contains_flow: { forward: "플로우 포함", backward: "플로우 내" },
|
||||
flow_step: { forward: "플로우 단계", backward: "단계의" },
|
||||
@@ -110,6 +264,9 @@ export const ko = {
|
||||
categorized_under: { forward: "카테고리화", backward: "카테고리화함" },
|
||||
authored_by: { forward: "작성자", backward: "작성" },
|
||||
},
|
||||
pathFinder: {
|
||||
title: "노드 간 경로 찾기 (P)",
|
||||
},
|
||||
};
|
||||
|
||||
export default ko;
|
||||
@@ -17,6 +17,10 @@ export const zhTW = {
|
||||
truncated: "(已截斷)",
|
||||
preview: "預覽",
|
||||
doubleClickToOpen: "雙擊開啟",
|
||||
appName: "Understand Anything",
|
||||
pressKeyboard: "按 ? 查看鍵盤快捷鍵",
|
||||
path: "路徑",
|
||||
theme: "主題",
|
||||
},
|
||||
projectOverview: {
|
||||
nodes: "節點",
|
||||
@@ -30,6 +34,7 @@ export const zhTW = {
|
||||
infra: "基礎設施",
|
||||
data: "資料",
|
||||
domain: "領域",
|
||||
knowledge: "知識",
|
||||
languages: "程式語言",
|
||||
frameworks: "框架",
|
||||
nodeTypeDistribution: "節點類型分布",
|
||||
@@ -73,6 +78,155 @@ export const zhTW = {
|
||||
deepDive: "深入",
|
||||
deepDiveDesc: "程式碼聚焦與對話",
|
||||
},
|
||||
sidebar: {
|
||||
info: "資訊",
|
||||
files: "檔案",
|
||||
},
|
||||
mobile: {
|
||||
graph: "圖谱",
|
||||
info: "資訊",
|
||||
files: "檔案",
|
||||
},
|
||||
drawer: {
|
||||
controls: "控制",
|
||||
dashboard: "儀表板",
|
||||
role: "角色",
|
||||
view: "視圖",
|
||||
diffOverlay: "差異覆蓋",
|
||||
nodeTypes: "節點類型",
|
||||
layers: "層級",
|
||||
tools: "工具",
|
||||
path: "路徑",
|
||||
help: "幫助",
|
||||
structural: "結構",
|
||||
domain: "領域",
|
||||
},
|
||||
domainView: {
|
||||
backToDomains: "返回領域列表",
|
||||
},
|
||||
detailLevel: {
|
||||
filesTitle: "僅檔案 — 架構級依賴(快速)",
|
||||
classesTitle: "檔案 + 類別 — 程式碼結構及繼承關係",
|
||||
files: "檔案",
|
||||
classes: "+類別",
|
||||
fnTitle: "切換函數節點(可能降低渲染速度)",
|
||||
fn: "函數",
|
||||
},
|
||||
nodeTypeLabels: {
|
||||
all: "全部",
|
||||
code: "程式碼",
|
||||
config: "配置",
|
||||
docs: "文件",
|
||||
infra: "基礎設施",
|
||||
data: "資料",
|
||||
domain: "領域",
|
||||
knowledge: "知識",
|
||||
},
|
||||
tokenGate: {
|
||||
validating: "驗證中...",
|
||||
continue: "繼續",
|
||||
},
|
||||
diffToggle: {
|
||||
hideOverlay: "隱藏差異覆蓋",
|
||||
showOverlay: "顯示差異覆蓋",
|
||||
noData: "未載入差異資料",
|
||||
changed: "已修改",
|
||||
affected: "受影響",
|
||||
},
|
||||
learnPanel: {
|
||||
finish: "完成",
|
||||
next: "下一步",
|
||||
prev: "上一步",
|
||||
noTour: "無導覽可用",
|
||||
noTourHint: "從知識圖谱生成導覽以獲取程式碼庫的引導式講解",
|
||||
projectTour: "專案導覽",
|
||||
steps: "步",
|
||||
stepsTitle: "步驟",
|
||||
guidedWalkthrough: "程式碼庫引導式講解",
|
||||
startTour: "開始導覽",
|
||||
tour: "導覽",
|
||||
exitTour: "退出導覽",
|
||||
},
|
||||
layer: {
|
||||
defaultName: "層級",
|
||||
label: "層",
|
||||
},
|
||||
breadcrumb: {
|
||||
projectOverview: "專案概覽",
|
||||
project: "專案",
|
||||
escBack: "按 Esc 返回",
|
||||
},
|
||||
warningBanner: {
|
||||
dropped: "已捨棄",
|
||||
fatal: "致命錯誤",
|
||||
},
|
||||
themePicker: {
|
||||
changeTheme: "變更主題",
|
||||
theme: "主題",
|
||||
accentColor: "強調色",
|
||||
headingFont: "標題字型",
|
||||
serif: "襯線",
|
||||
sans: "無襯線",
|
||||
mono: "等寬",
|
||||
},
|
||||
codeViewer: {
|
||||
fullFile: "完整檔案",
|
||||
lines: "行",
|
||||
linesLabel: "行",
|
||||
noFile: "未選擇檔案",
|
||||
loading: "載入原始碼中...",
|
||||
openLarger: "開啟更大的程式碼檢視器",
|
||||
closeExpanded: "關閉展開的程式碼檢視器",
|
||||
closeViewer: "關閉程式碼檢視器",
|
||||
sourceUnavailable: "原始碼不可用",
|
||||
},
|
||||
customNode: {
|
||||
tested: "已測試",
|
||||
hasTests: "有測試",
|
||||
},
|
||||
ariaLabels: {
|
||||
openMenu: "開啟選單",
|
||||
closeMenu: "關閉選單",
|
||||
settings: "設定",
|
||||
hideSearch: "隱藏搜尋",
|
||||
showSearch: "顯示搜尋",
|
||||
},
|
||||
nodeTypeFilter: {
|
||||
hide: "隱藏",
|
||||
show: "顯示",
|
||||
nodesLabel: "節點",
|
||||
},
|
||||
keyboardShortcuts: {
|
||||
showHelp: "顯示鍵盤快捷鍵",
|
||||
general: "一般",
|
||||
navigation: "導航",
|
||||
tour: "導覽",
|
||||
view: "檢視",
|
||||
focusSearch: "聚焦搜尋列",
|
||||
nextStep: "下一步導覽",
|
||||
prevStep: "上一步導覽",
|
||||
toggleDiff: "切換差異模式",
|
||||
toggleFilter: "切換篩選面板",
|
||||
toggleExport: "切換匯出選單",
|
||||
openPathFinder: "開啟路徑尋找器",
|
||||
title: "鍵盤快捷鍵",
|
||||
toggleHint: "按 ? 隨時切換此幫助",
|
||||
closeHint: "按 ESC 關閉",
|
||||
escapeDesc: "關閉面板和彈窗 / 返回概覽",
|
||||
},
|
||||
search: {
|
||||
placeholder: "搜尋節點名稱、摘要或標籤...",
|
||||
fuzzy: "模糊",
|
||||
semantic: "語意",
|
||||
result: "結果",
|
||||
},
|
||||
export: {
|
||||
label: "匯出",
|
||||
title: "匯出圖谱 (E)",
|
||||
asPNG: "匯出為 PNG",
|
||||
asSVG: "匯出為 SVG",
|
||||
asJSON: "匯出為 JSON",
|
||||
},
|
||||
edgeLabels: {
|
||||
imports: { forward: "導入", backward: "被導入" },
|
||||
exports: { forward: "導出到", backward: "被導出" },
|
||||
@@ -110,6 +264,9 @@ export const zhTW = {
|
||||
categorized_under: { forward: "归类於", backward: "归类" },
|
||||
authored_by: { forward: "作者", backward: "著作" },
|
||||
},
|
||||
pathFinder: {
|
||||
title: "尋找節點間路徑 (P)",
|
||||
},
|
||||
};
|
||||
|
||||
export default zhTW;
|
||||
@@ -17,6 +17,10 @@ export const zh = {
|
||||
truncated: "(已截断)",
|
||||
preview: "预览",
|
||||
doubleClickToOpen: "双击打开",
|
||||
appName: "Understand Anything",
|
||||
pressKeyboard: "按 ? 查看键盘快捷键",
|
||||
path: "路径",
|
||||
theme: "主题",
|
||||
},
|
||||
projectOverview: {
|
||||
nodes: "节点",
|
||||
@@ -30,6 +34,7 @@ export const zh = {
|
||||
infra: "基础设施",
|
||||
data: "数据",
|
||||
domain: "领域",
|
||||
knowledge: "知识",
|
||||
languages: "编程语言",
|
||||
frameworks: "框架",
|
||||
nodeTypeDistribution: "节点类型分布",
|
||||
@@ -73,6 +78,155 @@ export const zh = {
|
||||
deepDive: "深入",
|
||||
deepDiveDesc: "代码聚焦与对话",
|
||||
},
|
||||
sidebar: {
|
||||
info: "信息",
|
||||
files: "文件",
|
||||
},
|
||||
mobile: {
|
||||
graph: "图谱",
|
||||
info: "信息",
|
||||
files: "文件",
|
||||
},
|
||||
drawer: {
|
||||
controls: "控制",
|
||||
dashboard: "仪表盘",
|
||||
role: "角色",
|
||||
view: "视图",
|
||||
diffOverlay: "差异覆盖",
|
||||
nodeTypes: "节点类型",
|
||||
layers: "层级",
|
||||
tools: "工具",
|
||||
path: "路径",
|
||||
help: "帮助",
|
||||
structural: "结构",
|
||||
domain: "领域",
|
||||
},
|
||||
domainView: {
|
||||
backToDomains: "返回领域列表",
|
||||
},
|
||||
detailLevel: {
|
||||
filesTitle: "仅文件 — 架构级依赖(快速)",
|
||||
classesTitle: "文件 + 类 — 代码结构及继承关系",
|
||||
files: "文件",
|
||||
classes: "+类",
|
||||
fnTitle: "切换函数节点(可能降低渲染速度)",
|
||||
fn: "函数",
|
||||
},
|
||||
nodeTypeLabels: {
|
||||
all: "全部",
|
||||
code: "代码",
|
||||
config: "配置",
|
||||
docs: "文档",
|
||||
infra: "基础设施",
|
||||
data: "数据",
|
||||
domain: "领域",
|
||||
knowledge: "知识",
|
||||
},
|
||||
tokenGate: {
|
||||
validating: "验证中...",
|
||||
continue: "继续",
|
||||
},
|
||||
diffToggle: {
|
||||
hideOverlay: "隐藏差异覆盖",
|
||||
showOverlay: "显示差异覆盖",
|
||||
noData: "未加载差异数据",
|
||||
changed: "已修改",
|
||||
affected: "受影响",
|
||||
},
|
||||
learnPanel: {
|
||||
finish: "完成",
|
||||
next: "下一步",
|
||||
prev: "上一步",
|
||||
noTour: "无导览可用",
|
||||
noTourHint: "从知识图谱生成导览以获取代码库的引导式讲解",
|
||||
projectTour: "项目导览",
|
||||
steps: "步",
|
||||
stepsTitle: "步骤",
|
||||
guidedWalkthrough: "代码库引导式讲解",
|
||||
startTour: "开始导览",
|
||||
tour: "导览",
|
||||
exitTour: "退出导览",
|
||||
},
|
||||
layer: {
|
||||
defaultName: "层级",
|
||||
label: "层",
|
||||
},
|
||||
breadcrumb: {
|
||||
projectOverview: "项目概览",
|
||||
project: "项目",
|
||||
escBack: "按 Esc 返回",
|
||||
},
|
||||
warningBanner: {
|
||||
dropped: "已丢弃",
|
||||
fatal: "致命错误",
|
||||
},
|
||||
themePicker: {
|
||||
changeTheme: "更换主题",
|
||||
theme: "主题",
|
||||
accentColor: "强调色",
|
||||
headingFont: "标题字体",
|
||||
serif: "衬线",
|
||||
sans: "无衬线",
|
||||
mono: "等宽",
|
||||
},
|
||||
codeViewer: {
|
||||
fullFile: "完整文件",
|
||||
lines: "行",
|
||||
linesLabel: "行",
|
||||
noFile: "未选择文件",
|
||||
loading: "加载源码中...",
|
||||
openLarger: "打开更大的代码查看器",
|
||||
closeExpanded: "关闭展开的代码查看器",
|
||||
closeViewer: "关闭代码查看器",
|
||||
sourceUnavailable: "源码不可用",
|
||||
},
|
||||
customNode: {
|
||||
tested: "已测试",
|
||||
hasTests: "有测试",
|
||||
},
|
||||
ariaLabels: {
|
||||
openMenu: "打开菜单",
|
||||
closeMenu: "关闭菜单",
|
||||
settings: "设置",
|
||||
hideSearch: "隐藏搜索",
|
||||
showSearch: "显示搜索",
|
||||
},
|
||||
nodeTypeFilter: {
|
||||
hide: "隐藏",
|
||||
show: "显示",
|
||||
nodesLabel: "节点",
|
||||
},
|
||||
keyboardShortcuts: {
|
||||
showHelp: "显示键盘快捷键",
|
||||
general: "通用",
|
||||
navigation: "导航",
|
||||
tour: "导览",
|
||||
view: "视图",
|
||||
focusSearch: "聚焦搜索栏",
|
||||
nextStep: "下一步导览",
|
||||
prevStep: "上一步导览",
|
||||
toggleDiff: "切换差异模式",
|
||||
toggleFilter: "切换筛选面板",
|
||||
toggleExport: "切换导出菜单",
|
||||
openPathFinder: "打开路径查找器",
|
||||
title: "键盘快捷键",
|
||||
toggleHint: "按 ? 随时切换此帮助",
|
||||
closeHint: "按 ESC 关闭",
|
||||
escapeDesc: "关闭面板和弹窗 / 返回概览",
|
||||
},
|
||||
search: {
|
||||
placeholder: "搜索节点名称、摘要或标签...",
|
||||
fuzzy: "模糊",
|
||||
semantic: "语义",
|
||||
result: "结果",
|
||||
},
|
||||
export: {
|
||||
label: "导出",
|
||||
title: "导出图谱 (E)",
|
||||
asPNG: "导出为 PNG",
|
||||
asSVG: "导出为 SVG",
|
||||
asJSON: "导出为 JSON",
|
||||
},
|
||||
edgeLabels: {
|
||||
imports: { forward: "导入", backward: "被导入" },
|
||||
exports: { forward: "导出到", backward: "被导出" },
|
||||
@@ -110,6 +264,9 @@ export const zh = {
|
||||
categorized_under: { forward: "归类于", backward: "归类" },
|
||||
authored_by: { forward: "作者", backward: "著作" },
|
||||
},
|
||||
pathFinder: {
|
||||
title: "查找节点间路径 (P)",
|
||||
},
|
||||
};
|
||||
|
||||
export default zh;
|
||||
Reference in New Issue
Block a user