import type { ChangelogSection, ChangelogSectionTitle } from "@/lib/changelog-types"; /** Use a short paragraph when a release has very few notes. */ const PARAGRAPH_ITEM_THRESHOLD = 4; export function stripChangelogMarkdown(text: string): string { return text .replace(/`([^`]+)`/g, "$1") .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/\s+/g, " ") .trim(); } export function countReleaseItems(sections: ChangelogSection[]): number { return sections.reduce((total, section) => total + section.items.length, 0); } export function shouldUseParagraphSummary(sections: ChangelogSection[]): boolean { return countReleaseItems(sections) <= PARAGRAPH_ITEM_THRESHOLD; } const SECTION_VERBS: Partial> = { Added: "introduces", Changed: "improves", Fixed: "fixes", Removed: "removes", Deprecated: "deprecates", Security: "patches", }; function joinNaturalList(items: string[]): string { if (items.length === 0) return ""; if (items.length === 1) return items[0]; if (items.length === 2) return `${items[0]} and ${items[1]}`; return `${items.slice(0, -1).join(", ")}, and ${items[items.length - 1]}`; } export function buildReleaseParagraph(sections: ChangelogSection[]): string { const clauses = sections .map((section) => { const items = section.items.map((item) => stripChangelogMarkdown(item).replace(/\.$/, "").toLowerCase() ); const verb = SECTION_VERBS[section.title] ?? "updates"; return `${verb} ${joinNaturalList(items)}`; }) .filter(Boolean); if (clauses.length === 0) return ""; if (clauses.length === 1) { return `This release ${clauses[0]}.`; } const last = clauses[clauses.length - 1]; const rest = clauses.slice(0, -1); return `This release ${rest.join(", ")}, and ${last}.`; } function toLatinDigits(value: string): string { return value.replace(/[\u0660-\u0669\u06f0-\u06f9]/g, (digit) => String( digit.charCodeAt(0) - (digit <= "\u0669" ? 0x0660 : 0x06f0) ) ); } export function formatReleaseDate(date: string): string { const parsed = new Date(`${date}T00:00:00`); if (Number.isNaN(parsed.getTime())) return date; const formatted = new Intl.DateTimeFormat("en-US", { month: "long", day: "numeric", year: "numeric", numberingSystem: "latn", }).format(parsed); return toLatinDigits(formatted); }