import type { ReactNode } from "react" import Image from "next/image" import { getMessages, getTranslations } from "next-intl/server" import { Archive, Boxes, Braces, CheckCircle2, Cpu, Database, Download, ExternalLink, FileText, FolderTree, HardDrive, Layers3, Network, RefreshCw, ShieldCheck, SquareTerminal, Waypoints, } from "lucide-react" import { Link } from "@/i18n/navigation" import { Callout } from "@/components/ui/callout" import { DataFlowDiagram } from "@/components/ui/data-flow-diagram" import { DocHeader } from "@/components/ui/doc-header" import { Mermaid } from "@/components/ui/mermaid" import { STACK_DEPENDENCY_HOOK_SOURCE } from "@/components/oci/stackDependencyHookSource" /* * A documentation page described entirely by its message file. * * The page JSON holds `header` and an ordered list of `sections`, each with * an optional title and intro and a list of blocks. A block is an object * with a single key naming its kind ({"p": "..."}, {"table": {...}}), so no * structural value is ever a translatable string. Values the translation * pipeline must leave alone live under keys it copies verbatim: `code`, * `chartCode`, `icon`, `src`, `href`, `id`. * * Prose goes through t.rich, so it may use , , and the * link tags the page declares. */ type Rich = (chunks: ReactNode) => ReactNode type Json = Record const ICONS: Record = { archive: , boxes: , braces: , cpu: , database: , fileText: , folderTree: , hardDrive: , layers: , network: , refresh: , shield: , terminal: , waypoints: , } const CALLOUTS = { calloutInfo: "info", calloutTip: "tip", calloutWarning: "warning", calloutDanger: "danger", } as const const SNIPPETS: Record = { stackDependencyHook: STACK_DEPENDENCY_HOOK_SOURCE, } function at(root: Json, path: string): any { return path.split(".").reduce((node, key) => (node == null ? undefined : node[key]), root as any) } export async function DocPage({ locale, namespace, minutes, links = {}, }: { locale: string namespace: string minutes: number links?: Record }) { const t = await getTranslations({ locale, namespace }) const messages = (await getMessages({ locale })) as Json const page = at(messages, namespace) as Json const tags: Record = { code: (chunks) => {chunks}, strong: (chunks) => {chunks}, em: (chunks) => {chunks}, } for (const [name, href] of Object.entries(links)) { tags[name] = href.startsWith("http") ? (chunks) => ( {chunks} ) : (chunks) => {chunks} } const rich = (key: string) => t.rich(key, tags) const block = (key: string, value: Json, index: number): ReactNode => { const [kind] = Object.keys(value) const base = `${key}.${kind}` const data = value[kind] switch (kind) { case "p": return

{rich(base)}

case "calloutInfo": case "calloutTip": case "calloutWarning": case "calloutDanger": return ( {rich(`${base}.body`)} ) case "list": return (
    {data.items.map((_: string, i: number) => (
  • {rich(`${base}.items.${i}`)}
  • ))}
) case "steps": return (
    {data.items.map((_: Json, i: number) => (
  1. {i + 1}

    {t(`${base}.items.${i}.title`)}

    {rich(`${base}.items.${i}.body`)}

  2. ))}
) case "cards": return (
{data.items.map((item: Json, i: number) => (
{item.icon && ICONS[item.icon] && (
{ICONS[item.icon]}
)}

{t(`${base}.items.${i}.title`)}

{rich(`${base}.items.${i}.body`)}

))}
) case "table": return (
{data.headers.map((_: string, i: number) => ( ))} {data.rows.map((row: string[], r: number) => ( {row.map((_: string, c: number) => ( ))} ))}
{t(`${base}.headers.${i}`)}
{rich(`${base}.rows.${r}.${c}`)}
) case "flow": return ( ({ label: t(`${base}.nodes.${i}.label`), detail: data.nodes[i].detail ? t(`${base}.nodes.${i}.detail`) : undefined, variant: i === 0 ? "source" : i === data.nodes.length - 1 ? "target" : "bridge", }))} caption={data.caption ? t(`${base}.caption`) : undefined} /> ) case "code": return (
{data.title &&
{t(`${base}.title`)}
}
{data.code}
) case "codeGrid": return (
{data.items.map((item: Json, i: number) => (
{t(`${base}.items.${i}.title`)}
{item.code}
))}
) case "shell": return (
{data.code}
) case "mermaid": { let chart: string = data.chartCode for (const name of Object.keys(data.labels ?? {})) { chart = chart.split(`{{${name}}}`).join(t(`${base}.labels.${name}`).replace(/"/g, "'")) } return
} case "figure": return (
{t(`${base}.alt`)} {data.caption && (
{rich(`${base}.caption`)}
)}
) case "snippet": return (
{t(`${base}.summary`)} {data.pathCode}
              {SNIPPETS[data.snippetCode] ?? ""}
            
) case "downloads": return (
{data.items.map((item: Json, i: number) => (

{t(`${base}.items.${i}.title`)}

{rich(`${base}.items.${i}.body`)}

PDF
{(item.facts ?? []).map((_: Json, f: number) => (
{t(`${base}.items.${i}.facts.${f}.label`)}
{rich(`${base}.items.${i}.facts.${f}.value`)}
))}
))}
) case "next": return (
    {data.items.map((item: Json, i: number) => (
  • {t(`${base}.items.${i}.label`)} {item.tail ? <> — {t(`${base}.items.${i}.tail`)} : null}
  • ))}
) default: return null } } return (
{(page.sections as Json[]).map((section, s) => (
{section.title &&

{t(`sections.${s}.title`)}

} {section.intro &&

{rich(`sections.${s}.intro`)}

} {(section.blocks as Json[]).map((value, b) => block(`sections.${s}.blocks.${b}`, value, b))}
))}
) }