--- // 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 { t } from "@/i18n"; import { localizeHref } from "@/lib/i18n-page"; import { loadToolStrings } from "@/lib/tool-strings"; import type { ModalityFilter, ToolSearchItem } from "../lib/tool-search"; import SectionHeading from "./SectionHeading.astro"; interface Props { locale?: string; } const { locale = "en" } = Astro.props; // Tool name/description come from shared i18n (translated in all 21 langs). const toolStrings = await loadToolStrings(locale); const categoryMap = new Map(CATEGORIES.map((c) => [c.id, c])); type ToolModality = ToolSearchItem["modality"]; const modalities: { id: ModalityFilter; label: string; count: number }[] = [ { id: "all", label: t(locale, "home.toolGrid.filter.all"), count: TOOLS.length }, { id: "image", label: t(locale, "home.toolGrid.filter.image"), count: TOOLS.filter((tool) => tool.modality === "image").length, }, { id: "video", label: t(locale, "home.toolGrid.filter.video"), count: TOOLS.filter((tool) => tool.modality === "video").length, }, { id: "audio", label: t(locale, "home.toolGrid.filter.audio"), count: TOOLS.filter((tool) => tool.modality === "audio").length, }, { id: "document,file", label: t(locale, "home.toolGrid.filter.pdfFiles"), count: TOOLS.filter((tool) => tool.modality === "document" || tool.modality === "file").length, }, ]; const workflows = [ { id: "optimize", label: t(locale, "home.toolGrid.workflow.optimize.label"), description: t(locale, "home.toolGrid.workflow.optimize.description"), query: "optimize files for web", icon: "Gauge", metric: t(locale, "home.toolGrid.workflow.optimize.metric"), }, { id: "convert", label: t(locale, "home.toolGrid.workflow.convert.label"), description: t(locale, "home.toolGrid.workflow.convert.description"), query: "convert files to another format", icon: "Repeat2", metric: t(locale, "home.toolGrid.workflow.convert.metric"), }, { id: "pdf", label: t(locale, "home.toolGrid.workflow.pdf.label"), description: t(locale, "home.toolGrid.workflow.pdf.description"), query: "prepare pdf documents", icon: "FileStack", metric: t(locale, "home.toolGrid.workflow.pdf.metric"), }, { id: "metadata", label: t(locale, "home.toolGrid.workflow.metadata.label"), description: t(locale, "home.toolGrid.workflow.metadata.description"), query: "remove metadata", icon: "ShieldCheck", metric: t(locale, "home.toolGrid.workflow.metadata.metric"), }, { id: "batch", label: t(locale, "home.toolGrid.workflow.batch.label"), description: t(locale, "home.toolGrid.workflow.batch.description"), query: "batch file operations", icon: "ListChecks", metric: t(locale, "home.toolGrid.workflow.batch.metric"), }, { id: "ai", label: t(locale, "home.toolGrid.workflow.ai.label"), description: t(locale, "home.toolGrid.workflow.ai.description"), query: "ai enhance extract text remove background", icon: "Sparkles", metric: t(locale, "home.toolGrid.workflow.ai.metric"), }, ]; // User-facing strings the client search script renders dynamically. Passed via an // inlined JSON blob so the script localizes without any runtime fetch. const toolGridStrings = { suggested: t(locale, "home.toolGrid.suggested"), curatedTools: t(locale, "home.toolGrid.curatedTools", { count: "{count}" }), startingPoints: t(locale, "home.toolGrid.startingPoints", { modality: "{modality}" }), countOfTools: t(locale, "home.toolGrid.countOfTools", { count: "{count}", total: "{total}" }), recommended: t(locale, "home.toolGrid.recommended"), countOfMatches: t(locale, "home.toolGrid.countOfMatches", { count: "{count}", total: "{total}" }), requestOrRefine: t(locale, "home.toolGrid.requestOrRefine"), closeToolsOne: t(locale, "home.toolGrid.closeToolsOne", { count: "{count}" }), closeToolsOther: t(locale, "home.toolGrid.closeToolsOther", { count: "{count}" }), noConfidentMatch: t(locale, "home.toolGrid.noConfidentMatch"), }; const workflowAliasesByToolId: Record = { "bulk-rename": ["batch file operations", "rename many files"], "compress-pdf": ["prepare pdf documents", "optimize delivery"], "compress-video": ["optimize delivery", "compress media"], "convert-audio": ["convert formats", "audio converter"], "convert-document": ["convert formats", "prepare documents"], "convert-video": ["convert formats", "video converter"], "create-zip": ["batch file operations", "archive files"], "extract-zip": ["batch file operations", "extract archive"], "image-to-pdf": ["prepare documents", "convert images to pdf"], "merge-pdf": ["prepare pdf documents", "combine pdf"], "optimize-for-web": ["optimize delivery", "web delivery"], "pdfa-convert": ["prepare pdf documents", "archive pdf"], "redact-pdf": ["prepare pdf documents", "hide sensitive text"], "remove-background": ["enhance with ai", "transparent background"], "remove-metadata": ["clean metadata", "strip exif"], "split-pdf": ["prepare pdf documents", "extract pages"], "transcribe-audio": ["enhance with ai", "speech to text"], "watermark-pdf": ["prepare pdf documents", "add watermark"], "word-to-pdf": ["prepare documents", "convert document to pdf"], }; const defaultToolIds = [ "resize", "compress", "convert", "remove-background", "optimize-for-web", "ocr", "merge-pdf", "split-pdf", "compress-pdf", "compress-video", "convert-audio", "extract-zip", ]; 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(""); } function toToolSearchItem(tool: (typeof TOOLS)[number]): ToolSearchItem { const category = categoryMap.get(tool.category); return { id: tool.id, name: toolStrings[tool.id]?.name ?? tool.name, description: toolStrings[tool.id]?.description ?? tool.description, modality: tool.modality as ToolModality, category: tool.category, url: localizeHref(locale, `/tools/${toolSection(tool)}/${tool.id}/`), icon: tool.icon, iconSvg: renderIcon(tool.icon), color: category?.color ?? "#E07832", acceptedInputs: [...tool.acceptedInputs], outputModality: tool.outputModality as ToolModality | undefined, keywords: [...(tool.keywords ?? [])], workflowAliases: workflowAliasesByToolId[tool.id] ?? [], }; } const searchIndex = TOOLS.map(toToolSearchItem); const defaultTools = defaultToolIds .map((id) => searchIndex.find((tool) => tool.id === id)) .filter((tool): tool is ToolSearchItem => Boolean(tool)); const safeSearchIndexJson = JSON.stringify(searchIndex).replace(/
{modalities.map((modality) => ( ))}

{t(locale, "home.toolGrid.finderLabel")}

{t(locale, "home.toolGrid.suggested")}

{t(locale, "home.toolGrid.curatedTools", { count: defaultTools.length })}

{t(locale, "home.toolGrid.missingWorkflow")} {t(locale, "home.toolGrid.requestTool")} {t(locale, "home.toolGrid.requestSuffix")}
{t(locale, "home.toolGrid.browseCatalog")}