complete i18n migration to /[locale]/ with EN+ES content

Full rewrite of the docs site under app/[locale]/ with next-intl
in localePrefix:"always" mode. Every page now exists at both
/en/<path> and /es/<path>; the root / shows a meta-refresh + JS
redirect to /<defaultLocale>/ so GitHub Pages serves something
on the apex URL.

Highlights:
- 107 doc pages migrated to file-per-page JSON namespaces under
  messages/en/ and messages/es/. Spanish content is fully
  translated (no copy-of-English placeholders).
- New documentation for the Active Suppressions section in the
  Settings tab and the per-event Dismiss dropdown in the Health
  Monitor modal.
- New screenshots: dismiss-duration-dropdown.png and an updated
  health-suppression-settings.png.
- Pagefind integrated for client-side search; index is built on
  every CI deploy (not committed).
- RSS feeds: per-locale at /<locale>/rss.xml plus root /rss.xml
  for backward compat.
- Removed the dead app/[locale]/guides/[slug]/ route — every
  guide now has its own static page and no markdown source
  remains.
- Fixed orphan link /guides/nvidia -> /guides/nvidia-manual in
  docs/hardware/nvidia-host.
- Removed obsolete components (footer2, calendar, drawer).

Verified locally with `npm ci && npm run build`: 2804 files in
out/, 231 pages indexed by pagefind, root redirect intact, both
locale roots and the new Active Suppressions docs render OK.
This commit is contained in:
MacRimi
2026-05-31 12:41:10 +02:00
parent 875910b4d7
commit 5ca3463bf6
649 changed files with 83958 additions and 11096 deletions
+334 -92
View File
@@ -1,113 +1,295 @@
"use client"
import Link from "next/link"
import { usePathname } from "next/navigation"
// Use the locale-aware Link from next-intl so every sidebar href gets
// the active /en/ or /es/ prefix automatically. With `next/link` the
// hrefs were emitted without a locale (e.g. /docs/create-vm) and 404'd
// because the routing is configured with `localePrefix: "always"`.
import { Link, usePathname } from "@/i18n/navigation"
import { useState, useEffect } from "react"
import { ChevronDown, ChevronRight, Menu, X } from "lucide-react"
import { useTranslations } from "next-intl"
interface SubMenuItem {
title: string
i18nKey?: string // key under docSidebar.items.* in messages — falls back to `title` if absent
href: string
submenu?: SubMenuItem[]
}
interface MenuItem {
title: string
i18nKey?: string // key under docSidebar.items.* in messages — falls back to `title` if absent
href?: string
submenu?: SubMenuItem[]
}
function collectHrefs(items: SubMenuItem[]): string[] {
const out: string[] = []
for (const it of items) {
out.push(it.href)
if (it.submenu) out.push(...collectHrefs(it.submenu))
}
return out
}
export const sidebarItems: MenuItem[] = [
{ title: "Introduction", href: "/docs/introduction" },
{ title: "Installation", href: "/docs/installation" },
{ title: "Introduction", i18nKey: "introduction", href: "/docs/introduction" },
{ title: "Installation", i18nKey: "installation", href: "/docs/installation" },
{
title: "Post-Install Script",
title: "ProxMenux Monitor",
i18nKey: "proxmenuxMonitor",
submenu: [
{ title: "Overview", href: "/docs/post-install" },
{ title: "Basic Settings", href: "/docs/post-install/basic-settings" },
{ title: "System", href: "/docs/post-install/system" },
{ title: "Virtualization", href: "/docs/post-install/virtualization" },
{ title: "Network", href: "/docs/post-install/network" },
{ title: "Storage", href: "/docs/post-install/storage" },
{ title: "Security", href: "/docs/post-install/security" },
{ title: "Customization", href: "/docs/post-install/customization" },
{ title: "Monitoring", href: "/docs/post-install/monitoring" },
{ title: "Performance", href: "/docs/post-install/performance" },
{ title: "Optional", href: "/docs/post-install/optional" },
{ title: "Overview", i18nKey: "monitorOverview", href: "/docs/monitor" },
{ title: "Architecture", i18nKey: "architecture", href: "/docs/monitor/architecture" },
{ title: "Access & Authentication", i18nKey: "accessAuth", href: "/docs/monitor/access-auth" },
{
title: "Dashboard",
i18nKey: "dashboard",
href: "/docs/monitor/dashboard",
submenu: [
{ title: "System Overview tab", i18nKey: "dashboardSystemOverview", href: "/docs/monitor/dashboard/system-overview" },
{ title: "Storage tab", i18nKey: "dashboardStorage", href: "/docs/monitor/dashboard/storage" },
{ title: "Network tab", i18nKey: "dashboardNetwork", href: "/docs/monitor/dashboard/network" },
{ title: "VMs & LXCs tab", i18nKey: "dashboardVmsLxcs", href: "/docs/monitor/dashboard/vms-lxcs" },
{ title: "Hardware tab", i18nKey: "dashboardHardware", href: "/docs/monitor/dashboard/hardware" },
{ title: "System Logs tab", i18nKey: "dashboardSystemLogs", href: "/docs/monitor/dashboard/system-logs" },
{ title: "Terminal tab", i18nKey: "dashboardTerminal", href: "/docs/monitor/dashboard/terminal" },
{ title: "Security tab", i18nKey: "dashboardSecurity", href: "/docs/monitor/dashboard/security" },
{ title: "Settings tab", i18nKey: "dashboardSettings", href: "/docs/monitor/dashboard/settings" },
],
},
{ title: "Health Monitor", i18nKey: "healthMonitor", href: "/docs/monitor/health-monitor" },
{ title: "Notifications", i18nKey: "notifications", href: "/docs/monitor/notifications" },
{ title: "AI Assistant", i18nKey: "aiAssistant", href: "/docs/monitor/ai-assistant" },
{ title: "API Reference", i18nKey: "apiReference", href: "/docs/monitor/api" },
{ title: "Integrations", i18nKey: "integrations", href: "/docs/monitor/integrations" },
],
},
{
title: "Help and Info",
title: "ProxMenux Scripts",
i18nKey: "proxmenuxScripts",
submenu: [
{ title: "Overview", href: "/docs/help-info" },
{ title: "Useful System Commands", href: "/docs/help-info/system-commands" },
{ title: "VM and CT Management", href: "/docs/help-info/vm-ct-commands" },
{ title: "Storage and Disks", href: "/docs/help-info/storage-commands" },
{ title: "Network Commands", href: "/docs/help-info/network-commands" },
{ title: "Updates and Packages", href: "/docs/help-info/update-commands" },
{ title: "GPU Passthrough", href: "/docs/help-info/gpu-commands" },
{ title: "ZFS Management", href: "/docs/help-info/zfs-commands" },
{ title: "Backup and Restore", href: "/docs/help-info/backup-commands" },
{ title: "System CLI Tools", href: "/docs/help-info/tools-commands" },
{
title: "Post-Install Script",
i18nKey: "postInstallScript",
href: "/docs/post-install",
submenu: [
{ title: "Overview", i18nKey: "postInstallOverview", href: "/docs/post-install" },
{ title: "Automated", i18nKey: "postInstallAutomated", href: "/docs/post-install/automated" },
{
title: "Customizable",
i18nKey: "postInstallCustomizable",
href: "/docs/post-install/customizable",
submenu: [
{ title: "Basic Settings", i18nKey: "postInstallBasicSettings", href: "/docs/post-install/basic-settings" },
{ title: "System", i18nKey: "postInstallSystem", href: "/docs/post-install/system" },
{ title: "Virtualization", i18nKey: "postInstallVirtualization", href: "/docs/post-install/virtualization" },
{ title: "Network", i18nKey: "postInstallNetwork", href: "/docs/post-install/network" },
{ title: "Storage", i18nKey: "postInstallStorage", href: "/docs/post-install/storage" },
{ title: "Security", i18nKey: "postInstallSecurity", href: "/docs/post-install/security" },
{ title: "Customization", i18nKey: "postInstallCustomization", href: "/docs/post-install/customization" },
{ title: "Monitoring", i18nKey: "postInstallMonitoring", href: "/docs/post-install/monitoring" },
{ title: "Performance", i18nKey: "postInstallPerformance", href: "/docs/post-install/performance" },
{ title: "Optional", i18nKey: "postInstallOptional", href: "/docs/post-install/optional" },
],
},
{ title: "Apply Available Updates", i18nKey: "postInstallUpdates", href: "/docs/post-install/updates" },
{ title: "Uninstall Optimizations", i18nKey: "postInstallUninstall", href: "/docs/post-install/uninstall" },
],
},
{
title: "GPUs and Coral-TPU",
i18nKey: "gpusCoralTpu",
href: "/docs/hardware/nvidia-host",
submenu: [
{ title: "Install NVIDIA Drivers (Host)", i18nKey: "nvidiaHost", href: "/docs/hardware/nvidia-host" },
{ title: "Install Coral TPU (Host)", i18nKey: "coralHost", href: "/docs/hardware/install-coral-tpu-host" },
{ title: "Add GPU to LXC", i18nKey: "addGpuLxc", href: "/docs/hardware/igpu-acceleration-lxc" },
{ title: "Add Coral TPU to LXC", i18nKey: "addCoralLxc", href: "/docs/hardware/coral-tpu-lxc" },
{ title: "Add GPU to VM (Passthrough)", i18nKey: "addGpuVm", href: "/docs/hardware/gpu-vm-passthrough" },
{ title: "Switch GPU Mode (VM ↔ LXC)", i18nKey: "switchGpuMode", href: "/docs/hardware/switch-gpu-mode" },
],
},
{
title: "Create VM",
i18nKey: "createVm",
href: "/docs/create-vm",
submenu: [
{ title: "Overview", i18nKey: "createVmOverview", href: "/docs/create-vm" },
{ title: "System NAS", i18nKey: "createVmSystemNas", href: "/docs/create-vm/system-nas" },
{ title: "Synology VM", i18nKey: "createVmSynology", href: "/docs/create-vm/system-nas/synology" },
{ title: "Others System NAS", i18nKey: "createVmNasOthers", href: "/docs/create-vm/system-nas/system-nas-others" },
{ title: "System Windows", i18nKey: "createVmSystemWindows", href: "/docs/create-vm/system-windows" },
{ title: "System Linux", i18nKey: "createVmSystemLinux", href: "/docs/create-vm/system-linux" },
],
},
{
title: "Disk Manager",
i18nKey: "diskManager",
href: "/docs/disk-manager",
submenu: [
{ title: "Overview", i18nKey: "diskManagerOverview", href: "/docs/disk-manager" },
{ title: "Import Disk to VM", i18nKey: "diskImportVm", href: "/docs/disk-manager/import-disk-vm" },
{ title: "Import Disk Image to VM", i18nKey: "diskImportImageVm", href: "/docs/disk-manager/import-disk-image-vm" },
{ title: "Add Controller or NVMe to VM", i18nKey: "diskAddController", href: "/docs/disk-manager/add-controller-nvme-vm" },
{ title: "Import Disk to LXC", i18nKey: "diskImportLxc", href: "/docs/disk-manager/import-disk-lxc" },
{ title: "Format / Wipe Physical Disk", i18nKey: "diskFormat", href: "/docs/disk-manager/format-disk" },
{ title: "SMART Disk Health & Test", i18nKey: "diskSmart", href: "/docs/disk-manager/smart-disk-test" },
],
},
{
title: "Storage & Share Manager",
i18nKey: "storageShareManager",
href: "/docs/storage-share",
submenu: [
{ title: "Overview", i18nKey: "storageShareOverview", href: "/docs/storage-share" },
{
title: "Host storage integration",
i18nKey: "hostStorage",
href: "/docs/storage-share#host",
submenu: [
{ title: "Add NFS share as Proxmox storage", i18nKey: "hostNfs", href: "/docs/storage-share/host-nfs" },
{ title: "Add Samba share as Proxmox storage", i18nKey: "hostSamba", href: "/docs/storage-share/host-samba" },
{ title: "Add iSCSI target as Proxmox storage", i18nKey: "hostIscsi", href: "/docs/storage-share/host-iscsi" },
{ title: "Add local disk as Proxmox storage", i18nKey: "hostLocalDisk", href: "/docs/storage-share/host-local-disk" },
{ title: "Add shared directory on Host", i18nKey: "hostLocalShared", href: "/docs/storage-share/host-local-shared" },
],
},
{ title: "LXC Mount Points (Host ↔ CT)", i18nKey: "lxcMountPoints", href: "/docs/storage-share/lxc-mount-points" },
{
title: "LXC network sharing",
i18nKey: "lxcNetworkSharing",
href: "/docs/storage-share#lxc-net",
submenu: [
{ title: "NFS client in LXC", i18nKey: "lxcNfsClient", href: "/docs/storage-share/lxc-nfs-client" },
{ title: "Samba client in LXC", i18nKey: "lxcSambaClient", href: "/docs/storage-share/lxc-samba-client" },
{ title: "NFS server in LXC", i18nKey: "lxcNfsServer", href: "/docs/storage-share/lxc-nfs-server" },
{ title: "Samba server in LXC", i18nKey: "lxcSambaServer", href: "/docs/storage-share/lxc-samba-server" },
],
},
],
},
{
title: "Network",
i18nKey: "network",
href: "/docs/network",
submenu: [
{ title: "Overview", i18nKey: "networkOverview", href: "/docs/network" },
{ title: "Diagnostics", i18nKey: "networkDiagnostics", href: "/docs/network/diagnostics" },
{ title: "Live monitoring tools", i18nKey: "networkMonitoring", href: "/docs/network/monitoring" },
{ title: "Bridge analysis & repair", i18nKey: "networkBridge", href: "/docs/network/bridge-analysis" },
{ title: "Config analysis & cleanup", i18nKey: "networkConfig", href: "/docs/network/config-analysis" },
{ title: "Persistent interface names", i18nKey: "networkPersistent", href: "/docs/network/persistent-names" },
{ title: "Interfaces backup & restart", i18nKey: "networkBackup", href: "/docs/network/backup-restore" },
],
},
{
title: "Security",
i18nKey: "security",
href: "/docs/security",
submenu: [
{ title: "Overview", i18nKey: "securityOverview", href: "/docs/security" },
{ title: "Fail2Ban", i18nKey: "securityFail2ban", href: "/docs/security/fail2ban" },
{ title: "Lynis", i18nKey: "securityLynis", href: "/docs/security/lynis" },
],
},
{
title: "Utilities",
i18nKey: "utilities",
href: "/docs/utils",
submenu: [
{ title: "Overview", i18nKey: "utilsOverview", href: "/docs/utils" },
{ title: "UUP Dump ISO Creator", i18nKey: "utilsUupDump", href: "/docs/utils/UUp-Dump-ISO-Creator" },
{ title: "System Utilities Installer", i18nKey: "utilsSystemUtils", href: "/docs/utils/system-utils" },
{ title: "Proxmox System Update", i18nKey: "utilsSystemUpdate", href: "/docs/utils/system-update" },
{ title: "Upgrade PVE 8 to PVE 9", i18nKey: "utilsUpgradePve", href: "/docs/utils/upgrade-pve8-pve9" },
{ title: "Export VM to OVA / OVF", i18nKey: "utilsExportVm", href: "/docs/utils/export-vm" },
{ title: "Import VM from OVA / OVF", i18nKey: "utilsImportVm", href: "/docs/utils/import-vm" },
],
},
{
title: "Settings ProxMenux",
i18nKey: "settingsProxmenux",
href: "/docs/settings",
submenu: [
{ title: "Overview", i18nKey: "settingsOverview", href: "/docs/settings" },
{ title: "ProxMenux Monitor", i18nKey: "settingsMonitor", href: "/docs/settings/proxmenux-monitor" },
{ title: "Change Release Channel", i18nKey: "settingsBeta", href: "/docs/settings/beta-program" },
// "Change Language" is intentionally hidden until the translation
// install flow is reactivated. The page file at
// /docs/settings/change-language/page.tsx is preserved so we can
// restore this entry in a single line edit once the feature ships.
// { title: "Change Language", i18nKey: "settingsLanguage", href: "/docs/settings/change-language" },
{ title: "Show Version Information", i18nKey: "settingsVersion", href: "/docs/settings/show-version-information" },
{ title: "Uninstall ProxMenux", i18nKey: "settingsUninstall", href: "/docs/settings/uninstall-proxmenux" },
],
},
],
},
{
title: "GPUs and Coral",
title: "Commands Reference",
i18nKey: "commandsReference",
submenu: [
{ title: "HW iGPU acceleration to an LXC", href: "/docs/hardware/igpu-acceleration-lxc" },
{ title: "Coral TPU to an LXC", href: "/docs/hardware/coral-tpu-lxc" },
{ title: "Install Coral TPU on the Host", href: "/docs/hardware/install-coral-tpu-host" },
],
},
{
title: "Create VM",
submenu: [
{ title: "Overview", href: "/docs/create-vm" },
{ title: "System NAS", href: "/docs/create-vm/system-nas" },
{ title: "Synology VM", href: "/docs/create-vm/synology" },
{ title: "Others System NAS", href: "/docs/create-vm/system-nas/system-nas-others" },
{ title: "System Windows", href: "/docs/create-vm/system-windows" },
{ title: "UUP Dump ISO Creator", href: "/docs/utils/UUp-Dump-ISO-Creator" },
{ title: "System Linux", href: "/docs/create-vm/system-linux" },
],
},
{
title: "Storage",
submenu: [
{ title: "Disk Passthrough to a VM", href: "/docs/storage/disk-passthrough-vm" },
{ title: "Disk Passthrough to a CT", href: "/docs/storage/disk-passthrough-ct" },
{ title: "Import Disk Image to a VM", href: "/docs/storage/import-disk-image-vm" },
],
},
{
title: "Network",
submenu: [
{ title: "Verify Network", href: "/docs/network/verify-network" },
{ title: "Show IP Information", href: "/docs/network/show-ip-information" },
],
},
{
title: "Settings ProxMenux",
submenu: [
{ title: "Change Language", href: "/docs/settings/change-language" },
{ title: "Show Version Information", href: "/docs/settings/show-version-information" },
{ title: "Uninstall ProxMenux", href: "/docs/settings/uninstall-proxmenux" },
{ title: "Overview", i18nKey: "commandsOverview", href: "/docs/help-info" },
{ title: "Useful System Commands", i18nKey: "commandsSystem", href: "/docs/help-info/system-commands" },
{ title: "VM and CT Management", i18nKey: "commandsVmCt", href: "/docs/help-info/vm-ct-commands" },
{ title: "Storage and Disks", i18nKey: "commandsStorage", href: "/docs/help-info/storage-commands" },
{ title: "Network Commands", i18nKey: "commandsNetwork", href: "/docs/help-info/network-commands" },
{ title: "Updates and Packages", i18nKey: "commandsUpdates", href: "/docs/help-info/update-commands" },
{ title: "GPU Passthrough", i18nKey: "commandsGpu", href: "/docs/help-info/gpu-commands" },
{ title: "ZFS Management", i18nKey: "commandsZfs", href: "/docs/help-info/zfs-commands" },
{ title: "Backup and Restore", i18nKey: "commandsBackup", href: "/docs/help-info/backup-commands" },
{ title: "System CLI Tools", i18nKey: "commandsTools", href: "/docs/help-info/tools-commands" },
],
},
{ title: "Glossary", i18nKey: "glossary", href: "/docs/glossary" },
{
title: "About",
i18nKey: "about",
submenu: [
{ title: "Code of Conduct", href: "/docs/about/code-of-conduct" },
{ title: "FAQ", href: "/docs/about/faq" },
{ title: "Contributors", href: "/docs/about/contributors" },
{ title: "Overview", i18nKey: "aboutOverview", href: "/docs/about" },
{ title: "FAQ", i18nKey: "aboutFaq", href: "/docs/about/faq" },
{ title: "Contributors", i18nKey: "aboutContributors", href: "/docs/about/contributors" },
{ title: "Contributing", i18nKey: "aboutContributing", href: "/docs/about/contributing" },
{ title: "Code of Conduct", i18nKey: "aboutCodeOfConduct", href: "/docs/about/code-of-conduct" },
],
},
{ title: "External Repositories", href: "/docs/external-repositories" },
{ title: "External Repositories", i18nKey: "externalRepositories", href: "/docs/external-repositories" },
]
export default function DocSidebar() {
const pathname = usePathname()
const [openSections, setOpenSections] = useState<{ [key: string]: boolean }>({})
const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false)
const t = useTranslations("docSidebar")
// Resolve the visible label for a sidebar item. Prefer the translated
// entry under `docSidebar.items.<i18nKey>` and fall back to the literal
// `title` for items that haven't been keyed yet (so adding a new entry
// without remembering to add a translation still renders the English
// string instead of throwing).
const tItem = (item: { title: string; i18nKey?: string }) => {
if (!item.i18nKey) return item.title
try {
return t(`items.${item.i18nKey}`)
} catch {
return item.title
}
}
const toggleSection = (title: string) => {
setOpenSections((prev) => ({ ...prev, [title]: !prev[title] }))
@@ -128,35 +310,89 @@ export default function DocSidebar() {
return () => window.removeEventListener("resize", handleResize)
}, [])
const renderSubItem = (subItem: SubMenuItem, depth: number) => {
const hasChildren = !!subItem.submenu && subItem.submenu.length > 0
if (hasChildren) {
const descendantHrefs = collectHrefs(subItem.submenu!)
const containsActivePage =
subItem.href === pathname || descendantHrefs.includes(pathname)
const sectionKey = `${subItem.href}__${subItem.title}`
const isOpen = (openSections[sectionKey] ?? containsActivePage) || false
return (
<li key={subItem.href}>
<div className="flex items-stretch">
<Link
href={subItem.href}
onClick={() => setIsMobileMenuOpen(false)}
className={`flex-1 block p-2 rounded-l ${
pathname === subItem.href
? "bg-blue-500 text-white"
: containsActivePage
? "bg-blue-50 text-blue-900 font-medium"
: "text-gray-700 hover:bg-gray-200 hover:text-gray-900"
}`}
>
{tItem(subItem)}
</Link>
<button
type="button"
aria-label={isOpen ? "Collapse" : "Expand"}
onClick={() => toggleSection(sectionKey)}
className={`px-2 flex items-center rounded-r ${
containsActivePage && pathname !== subItem.href
? "bg-blue-50 text-blue-900 hover:bg-blue-100"
: "text-gray-500 hover:bg-gray-200"
}`}
>
{isOpen ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
</button>
</div>
{isOpen && (
<ul className="ml-4 mt-2 space-y-2">
{subItem.submenu!.map((nested) => renderSubItem(nested, depth + 1))}
</ul>
)}
</li>
)
}
return (
<li key={subItem.href}>
<Link
href={subItem.href}
className={`block p-2 rounded ${
pathname === subItem.href
? "bg-blue-500 text-white"
: "text-gray-700 hover:bg-gray-200 hover:text-gray-900"
}`}
onClick={() => setIsMobileMenuOpen(false)}
>
{tItem(subItem)}
</Link>
</li>
)
}
const renderMenuItem = (item: MenuItem) => {
if (item.submenu) {
const isOpen = openSections[item.title] || false
const containsActivePage = collectHrefs(item.submenu).includes(pathname)
const isOpen = (openSections[item.title] ?? containsActivePage) || false
return (
<li key={item.title} className="mb-2">
<button
onClick={() => toggleSection(item.title)}
className="flex items-center justify-between w-full text-left p-2 rounded hover:bg-gray-200"
className={`flex items-center justify-between w-full text-left p-2 rounded transition-colors ${
containsActivePage
? "bg-blue-100 text-blue-900 font-semibold hover:bg-blue-200"
: "hover:bg-gray-200"
}`}
>
<span>{item.title}</span>
<span>{tItem(item)}</span>
{isOpen ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
</button>
{isOpen && (
<ul className="ml-4 mt-2 space-y-2">
{item.submenu.map((subItem) => (
<li key={subItem.href}>
<Link
href={subItem.href}
className={`block p-2 rounded ${
pathname === subItem.href
? "bg-blue-500 text-white"
: "text-gray-700 hover:bg-gray-200 hover:text-gray-900"
}`}
onClick={() => setIsMobileMenuOpen(false)}
>
{subItem.title}
</Link>
</li>
))}
{item.submenu.map((subItem) => renderSubItem(subItem, 1))}
</ul>
)}
</li>
@@ -171,7 +407,7 @@ export default function DocSidebar() {
}`}
onClick={() => setIsMobileMenuOpen(false)}
>
{item.title}
{tItem(item)}
</Link>
</li>
)
@@ -186,16 +422,22 @@ export default function DocSidebar() {
onClick={toggleMobileMenu}
aria-label="Toggle menu"
>
<span className="font-semibold">Documentation</span>
<span className="font-semibold">{t("documentation")}</span>
{isMobileMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
</button>
</div>
{/* On desktop (lg+) the sidebar is FIXED to the left so it stays
in place while the main content scrolls — matches the
Docusaurus / Hermes docs UX. The layout adds `lg:pl-72` to
the <main> so the content isn't hidden beneath the sidebar.
On mobile the sidebar is still a slide-down drawer triggered
by the hamburger button above. */}
<nav
className={`fixed lg:static top-[104px] left-0 w-full lg:w-72 h-[calc(100vh-104px)] lg:h-[calc(100vh-64px)] bg-gray-100 p-4 lg:p-6 pt-16 lg:pt-6 transform ${
className={`fixed top-[104px] lg:top-16 left-0 w-full lg:w-72 h-[calc(100vh-104px)] lg:h-[calc(100vh-64px)] bg-gray-100 border-r border-gray-200 p-4 lg:p-6 pt-16 lg:pt-6 transform ${
isMobileMenuOpen ? "translate-y-0" : "-translate-y-full"
} lg:translate-y-0 transition-transform duration-300 ease-in-out overflow-y-auto z-30`}
>
<h2 className="text-lg font-semibold mb-4 text-gray-900 lg:mt-0 sr-only lg:not-sr-only">Documentation</h2>
<h2 className="text-lg font-semibold mb-4 text-gray-900 lg:mt-0 sr-only lg:not-sr-only">{t("documentation")}</h2>
<ul className="space-y-2">{sidebarItems.map(renderMenuItem)}</ul>
</nav>
</>