166 lines
4.7 KiB
TypeScript
166 lines
4.7 KiB
TypeScript
export const dynamic = "force-dynamic";
|
|
|
|
import type { Metadata } from "next";
|
|
import { notFound } from "next/navigation";
|
|
import { getAllGlossaryEntries } from "@/lib/api";
|
|
import { buildOgMeta, extractQuoteFromHl } from "@/lib/og";
|
|
import { GlossaryClient } from "../glossary-client";
|
|
import { JsonLd } from "@/components/seo/json-ld";
|
|
import { articleJsonLd } from "@/lib/jsonld";
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Diacritic → base letter mapping
|
|
// ---------------------------------------------------------------------------
|
|
|
|
const DIACRITIC_TO_BASE: Record<string, string> = {
|
|
Ā: "A",
|
|
Ä: "A",
|
|
Ī: "I",
|
|
É: "I",
|
|
Ū: "U",
|
|
Ü: "U",
|
|
Ṛ: "R",
|
|
Ṝ: "R",
|
|
Ś: "S",
|
|
Ç: "S",
|
|
Ṣ: "S",
|
|
Ñ: "N",
|
|
Ṇ: "N",
|
|
Ṅ: "N",
|
|
Ṭ: "T",
|
|
Ḍ: "D",
|
|
Ḥ: "H",
|
|
};
|
|
|
|
function getBaseLetter(term: string): string {
|
|
if (!term) return "?";
|
|
const first = term[0].toUpperCase();
|
|
return DIACRITIC_TO_BASE[first] || first;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Metadata
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
searchParams,
|
|
}: {
|
|
params: Promise<{ letter: string }>;
|
|
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
|
}): Promise<Metadata> {
|
|
const { letter } = await params;
|
|
const sp = await searchParams;
|
|
const l = letter.toUpperCase();
|
|
|
|
// If a specific term is referenced via ?term= param, show it in the OG image
|
|
const termParam = typeof sp.term === "string" ? sp.term : null;
|
|
if (termParam) {
|
|
const allEntries = await getAllGlossaryEntries();
|
|
const entry = allEntries.find(
|
|
(e) => e.term.toLowerCase() === termParam.toLowerCase(),
|
|
);
|
|
if (entry) {
|
|
// Strip HTML tags from definition for OG quote
|
|
const plainDef = entry.definitionHtml
|
|
.replace(/<[^>]*>/g, "")
|
|
.replace(/&[^;]+;/g, " ")
|
|
.trim();
|
|
|
|
// Check for ?hl= highlight param — extract actual selected text from definition
|
|
const hlQuote = extractQuoteFromHl(sp, [entry.definitionHtml]);
|
|
|
|
return buildOgMeta({
|
|
title: entry.term,
|
|
detail: "Glossary — Prabhupāda Says",
|
|
quote:
|
|
hlQuote ||
|
|
(plainDef.length > 260 ? plainDef.slice(0, 257) + "..." : plainDef),
|
|
});
|
|
}
|
|
}
|
|
|
|
// Check for ?hl= without ?term= — try to extract quote from all entries for this letter
|
|
const hlQuote = extractQuoteFromHl(sp);
|
|
if (hlQuote) {
|
|
return buildOgMeta({
|
|
title: `Glossary — ${l}`,
|
|
detail: "Sanskrit & Vedic terms",
|
|
quote: hlQuote,
|
|
});
|
|
}
|
|
|
|
// Default: letter-level OG
|
|
return buildOgMeta({
|
|
title: `Glossary — ${l}`,
|
|
detail: `Sanskrit & Vedic terms starting with ${l}`,
|
|
quote:
|
|
"A comprehensive glossary of Sanskrit and Vedic terms from the books of Śrīla Prabhupāda.",
|
|
});
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Page
|
|
// ---------------------------------------------------------------------------
|
|
|
|
export default async function GlossaryLetterPage({
|
|
params,
|
|
}: {
|
|
params: Promise<{ letter: string }>;
|
|
}) {
|
|
const { letter: rawLetter } = await params;
|
|
const activeLetter = rawLetter.toUpperCase();
|
|
|
|
// Validate — single letter A-Z
|
|
if (!/^[A-Z]$/.test(activeLetter)) notFound();
|
|
|
|
// Fetch all entries (cached by Next.js request dedup within a single render)
|
|
const allEntries = await getAllGlossaryEntries();
|
|
|
|
// Group all entries by base letter
|
|
const byLetter: Record<string, typeof allEntries> = {};
|
|
for (const entry of allEntries) {
|
|
const base = getBaseLetter(entry.term);
|
|
if (!byLetter[base]) byLetter[base] = [];
|
|
byLetter[base].push(entry);
|
|
}
|
|
|
|
// All available letters (sorted)
|
|
const letters = Object.keys(byLetter).sort();
|
|
|
|
// Entries for the active letter
|
|
const letterEntries = byLetter[activeLetter] || [];
|
|
if (letterEntries.length === 0) notFound();
|
|
|
|
// Prepare client data — only the active letter's entries
|
|
const clientEntries = letterEntries.map((e) => ({
|
|
term: e.term,
|
|
definition: e.definition,
|
|
definition_html: e.definitionHtml,
|
|
}));
|
|
|
|
// Letter counts for the nav
|
|
const letterCounts: Record<string, number> = {};
|
|
for (const [l, entries] of Object.entries(byLetter)) {
|
|
letterCounts[l] = entries.length;
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<JsonLd
|
|
data={articleJsonLd({
|
|
path: `/glossary/${rawLetter}`,
|
|
headline: `Glossary — ${activeLetter}`,
|
|
isPartOf: "Glossary of Sanskrit & Vedic Terms",
|
|
})}
|
|
/>
|
|
<GlossaryClient
|
|
entries={clientEntries}
|
|
activeLetter={activeLetter}
|
|
letters={letters}
|
|
letterCounts={letterCounts}
|
|
/>
|
|
</div>
|
|
);
|
|
}
|