A reusable, zero-build static template for multilingual curated-list sites β fork it, swap the data, push, done. The first site built with this template is useful-software (100+ Windows / macOS apps with auto-updated downloads).
- π Multilingual UI (δΈζ / English / ζ₯ζ¬θͺ) with browser-language auto-detection and persisted choice
- π― Two-axis filtering β primary axis (default: OS) + categories
- π Full-text search with match highlighting
- π Dark mode (system preference + manual toggle, persisted)
- πΌοΈ Detail modal with optional images / videos / YouTube embeds
- β Favorites stored in localStorage
- π Sort by curated order / name AβZ / ZβA
- π Hash-based deep links (
/#item-slugopens the modal directly β shareable URLs) - π₯ Download buttons that prefer auto-fetched URLs over manual ones over a homepage fallback
- π€
update-versions.mjsscript that walks GitHub-hosted entries and writes the latest release info to a sidecar JSON - πΌοΈ
render-og.mjsto regenerate the social-share image fromscripts/og-template.html - π SEO basics: OG / Twitter Card meta, JSON-LD WebSite schema, sitemap.xml + robots.txt
- π Pure static HTML/CSS/JS β no build step, deploys to GitHub Pages directly
# 1. Clone this template
git clone https://github.com/HowCanLove/useful-catalog-template my-vscode-list
cd my-vscode-list
rm -rf .git && git init # start fresh history
# 2. Open the project β the rest is just editing config:
# a. data.js your catalog entries (start with the 5 placeholders)
# b. i18n.js UI strings β search "{{TODO}}" at the top to know what to swap
# c. index.html search "{{" β every placeholder needs a real value
# d. scripts/og-template.html β same {{}} placeholders for the OG image
# 3. Generate the social-share image
node scripts/render-og.mjs
# 4. (If GitHub-hosted entries are in your data.js) auto-fill download URLs + versions
node scripts/update-versions.mjs
# 5. Push to a new GitHub repo + enable Pages
git add -A && git commit -m "Initial catalog"
gh repo create my-vscode-list --public --source=. --push
gh api -X POST repos/<you>/my-vscode-list/pages -f source[branch]=main -f source[path]=/
# 6. Verify Google Search Console + submit sitemap.xml (see SEO section)| File | What needs to change | When you can leave it alone |
|---|---|---|
data.js |
Always β your catalog entries | Never (that's the whole point) |
i18n.js |
meta.*, site.title, site.tagline, os.*, category.*, search.placeholder, count.total, footer.text |
If your catalog is a Windows / macOS software list |
index.html |
Every {{PLACEHOLDER}} in <head>; the four <button class="os-tab"> if your axis isn't OS |
If your axis is OS-shaped |
scripts/og-template.html |
Every {{PLACEHOLDER}} |
If you keep the example branding |
app.js |
The cats array inside buildCategoryChips() if your category keys differ from the defaults |
If you reuse the default categories |
style.css |
The header gradient (lines ~67 and ~76) for a custom brand colour | If purple-blue works for you |
robots.txt, sitemap.xml |
Replace {{YOUR_USERNAME}} and {{YOUR_REPO_NAME}} |
Never (URL is yours) |
scripts/update-versions.mjs was built for GitHub-hosted projects but the same pattern fits anything with a public API. Quick wins:
| Catalog domain | API to swap in | What to fetch |
|---|---|---|
| VS Code / Cursor extensions | marketplace.visualstudio.com/_apis/public/gallery/extensionquery |
Latest version + install URL |
| npm packages | registry.npmjs.org/<pkg> |
dist-tags.latest + tarball URL |
| Cargo crates | crates.io/api/v1/crates/<name> |
Latest stable version |
| Homebrew formulae | formulae.brew.sh/api/formula/<name>.json |
Version + bottle URL |
| AI prompts | (no API β script becomes a markdownβJSON converter) | Lift prompts from prompts/*.md files |
The pattern: read data.js, walk entries, hit the API, write versions.json, frontend merges it in via loadVersions() in app.js. Around 100 lines.
The "OS tabs" top bar can become any 3-4-way axis. The fields you need to keep in sync:
data.jsβ changeos: 'windows' | 'macos' | 'cross'to your values, e.g.os: 'chrome' | 'firefox' | 'edge' | 'cross'index.htmlβ update the four<button class="os-tab" data-os="...">to matchi18n.jsβos.all,os.windows,os.macos,os.cross(and theos.badge.*short forms)app.jsβosMatches()anddetectOS(). The "auto-detect device OS on first visit" logic only makes sense for an OS axis; gut it for axes that aren't OS-shaped.
The cross value is special β it shows up under all other tabs. Keep that semantic if you have a "works everywhere" option.
Add (e.g. Korean):
i18n.jsβSUPPORTED_LANGS.push({ code: 'ko', name: 'νκ΅μ΄', htmlLang: 'ko' })i18n.jsβ copy any existing locale block, translate every keydata.jsβ adddesc.ko: '...'to every entryindex.htmlβ add<meta property="og:locale:alternate" content="ko_KR">
Drop a language by removing it from those four places (and stripping the desc.<lang> keys to save bytes).
- Google Search Console β add property β verify via the HTML file method β submit
sitemap.xml - Bing Webmaster Tools β import from Search Console (one click) β submit sitemap
- ηΎεΊ¦η«ιΏεΉ³ε° β if you target zh-CN traffic
- opengraph.xyz β preview your OG card across every social platform
- Google Rich Results Test β verify the JSON-LD parses
MIT. Spin up as many sites as you want, no attribution required (but a link back is appreciated π).