bun i
bun run dev
- astro for the foundation (Markdown, Vite, Islands Architecture)
- preact for the Preact components
- chart.js for the Pie component for Portfolio
- vitest for unit testing
- @testing-library/preact for testing-library
- mermaid for mermaid diagrams for markdown
- unocss for utility-first CSS
- Vercel for Build Previews
- Github Pages for Production
Note: I added remark-mermaid into utils and slightly modified it so that it is always simple mode and always returns a pre + <div class="mermaid"></div>. This allows mermaid code blocks to be included.
UI strings live in src/i18n/locales/ and are served via a pure Preact context — no external i18n library required. Adding a new locale takes 3 steps:
Copy src/i18n/locales/en.ts to a new file, e.g. src/i18n/locales/es.ts. Translate all string values. The key structure must stay identical.
// src/i18n/locales/es.ts
const es = {
nav: { home: "Inicio", blog: "Blog", portfolio: "Portafolio" },
hero: { prefix: "Construyo ", highlight: "apps increíbles", ... },
// ... all other keys translated
} as const;
export default es;In src/i18n/context.tsx, import the new locale and add it to the locales map:
import en from "~/i18n/locales/en";
import es from "~/i18n/locales/es"; // add
const locales: Record<string, Translation> = { en, es }; // add esIn astro.config.mjs, add the locale code to the locales array:
i18n: {
locales: ["en", "es"], // add "es"
defaultLocale: "en",
routing: { prefixDefaultLocale: false },
},Then create src/pages/es/index.astro (mirroring src/pages/index.astro). Astro will set Astro.currentLocale to "es" for that page, which flows through the layout's locale prop into I18nProvider — all components then resolve strings from the new locale automatically.
Note on islands: All home-page sections are grouped inside
<HomeSections locale={...} client:visible />, which wraps them in a singleI18nProvider. Each Astroclient:*island is an isolated Preact tree, so any new island components that useuseTranslation()must also receivelocaleand render inside anI18nProvider.
For locales that aren't real BCP 47 language codes (e.g. a "backward" locale that reverses all strings for testing), use Astro's locale object syntax to decouple the URL path from the lang attribute:
// astro.config.mjs
i18n: {
locales: [
"en",
{ path: "backward", codes: ["en-x-backward"] },
],
defaultLocale: "en",
routing: { prefixDefaultLocale: false },
},path— the URL segment (/backward/)codes— what goes in<html lang="...">. Usingen-x-backwardis a valid BCP 47 private-use extension: screen readers treat it as English while still accurately describing the variant.
Because Astro.currentLocale resolves to the first entry in codes ("en-x-backward"), you must pass locale explicitly in the page rather than relying on Astro.currentLocale:
<!-- src/pages/backward/index.astro -->
<DefaultPageLayout description={description}>
<HomeSections locale="backward" ... client:visible />
</DefaultPageLayout>Then register "backward" in src/i18n/context.tsx alongside its locale file:
import backward from "~/i18n/locales/backward";
const locales: Record<string, Translation> = { en, backward };The current approach (one folder per locale, e.g. src/pages/es/index.astro) works but doesn't scale — each new locale requires duplicating every page file.
The better long-term solution is to use a dynamic [locale] route with getStaticPaths so a single file generates all locale variants:
---
// src/pages/[locale]/index.astro
export function getStaticPaths() {
return [{ params: { locale: "es" } }, { params: { locale: "fr" } }];
}
const { locale } = Astro.params;
---This should be done when adding a second language for real, so the routing stays maintainable.
dist- this is the output of the build
- GPlay BG: https://svgeneration.netlify.app/recipes/gplay/
- HeroPatterns: heropatterns.com