--- // biome-ignore-all lint/correctness/noUnusedImports: Astro template consumes component imports. // biome-ignore-all lint/correctness/noUnusedVariables: Astro template consumes frontmatter values. import { CATEGORIES, TOOLS, toolSection } from "@snapotter/shared"; import * as lucideIcons from "lucide"; import Footer from "@/components/Footer.astro"; import JsonLd from "@/components/JsonLd.astro"; import Navbar from "@/components/Navbar.astro"; import { LANDING_LOCALES, t } from "@/i18n"; import Base from "@/layouts/Base.astro"; import { localizeHref } from "@/lib/i18n-page"; import { loadToolStrings } from "@/lib/tool-strings"; export function getStaticPaths() { return LANDING_LOCALES.map((locale) => ({ params: { locale: locale === "en" ? undefined : locale }, props: { locale }, })); } const { locale } = Astro.props; const PATH = "/tools"; const toolStrings = await loadToolStrings(locale); const toolName = (id: string, fallback: string) => toolStrings[id]?.name ?? fallback; const toolDesc = (id: string, fallback: string) => toolStrings[id]?.description ?? fallback; function renderIcon(iconName: string): string { const iconData = (lucideIcons as Record)[iconName]; if (!iconData || !Array.isArray(iconData)) return ''; return iconData .map(([tag, attrs]: [string, Record]) => { const attrStr = Object.entries(attrs) .map(([k, v]) => `${k}="${v}"`) .join(" "); return `<${tag} ${attrStr}/>`; }) .join(""); } const groups = CATEGORIES.map((cat) => ({ category: cat, tools: TOOLS.filter((t) => t.category === cat.id), })).filter((group) => group.tools.length > 0); const toolCount = TOOLS.length; const modalityFilters = [ { id: "all", label: t(locale, "toolsIndex.filter.all"), count: TOOLS.length }, { id: "image", label: t(locale, "toolsIndex.filter.image"), count: TOOLS.filter((t) => t.modality === "image").length, }, { id: "video", label: t(locale, "toolsIndex.filter.video"), count: TOOLS.filter((t) => t.modality === "video").length, }, { id: "audio", label: t(locale, "toolsIndex.filter.audio"), count: TOOLS.filter((t) => t.modality === "audio").length, }, { id: "document", label: t(locale, "toolsIndex.filter.document"), count: TOOLS.filter((t) => t.modality === "document").length, }, { id: "file", label: t(locale, "toolsIndex.filter.file"), count: TOOLS.filter((t) => t.modality === "file").length, }, ]; const searchPlaceholder = t(locale, "toolsIndex.searchPlaceholder", { count: toolCount }); const showingAll = t(locale, "toolsIndex.showingAll", { count: toolCount }); // Templates the client script fills in with live counts (kept build-time to avoid // bundling the i18n catalog into browser JS). const showingCountTpl = t(locale, "toolsIndex.showingCount"); const showingCountAllTpl = t(locale, "toolsIndex.showingCountAll"); const breadcrumbJsonLd = { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [ { "@type": "ListItem", position: 1, name: "Home", item: "https://snapotter.com" }, { "@type": "ListItem", position: 2, name: "Tools", item: "https://snapotter.com/tools" }, ], }; const collectionJsonLd = { "@context": "https://schema.org", "@type": "CollectionPage", name: "SnapOtter File Processing Tools", description: `${toolCount} self-hosted file processing tools with local AI. Resize, compress, convert, remove backgrounds, upscale, OCR, and more.`, url: "https://snapotter.com/tools", isPartOf: { "@type": "WebSite", name: "SnapOtter", url: "https://snapotter.com", }, numberOfItems: toolCount, mainEntity: { "@type": "ItemList", numberOfItems: toolCount, itemListElement: TOOLS.map((tool, i) => ({ "@type": "ListItem", position: i + 1, name: tool.name, url: `https://snapotter.com/tools/${toolSection(tool)}/${tool.id}/`, })), }, }; ---

{t(locale, "toolsIndex.title", { count: toolCount })}

{t(locale, "toolsIndex.subtitle")}

{modalityFilters.map((m) => ( ))}

{showingAll}

{groups.map((group) => (
{group.category.icon === "Layers" && <>} {group.category.icon === "Zap" && <>} {group.category.icon === "SlidersHorizontal" && <>} {group.category.icon === "Stamp" && <>} {group.category.icon === "Wrench" && <>} {group.category.icon === "LayoutGrid" && <>} {group.category.icon === "FileType" && <>} {group.category.icon === "Video" && <>} {group.category.icon === "AudioLines" && <>} {group.category.icon === "FileText" && <>} {group.category.icon === "Table" && <>} {group.category.icon === "Sparkles" && <>}

{t(locale, `category.${group.category.id}`)}

{group.tools.length === 1 ? t(locale, "toolsIndex.toolCountOne", { count: group.tools.length }) : t(locale, "toolsIndex.toolCountOther", { count: group.tools.length })}
))}

{t(locale, "toolsIndex.ctaTitle", { count: toolCount })}

{t(locale, "toolsIndex.ctaBody")}