diff --git a/CLAUDE.md b/CLAUDE.md index 5b6276a..848060d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -29,7 +29,7 @@ Astro 6 static site, based on the `blog` starter template. No tests, no linter c - Because posts sit two levels deep, hero images and component imports inside MD/MDX use `../../../assets/…` / `../../../components/…` relative paths. - UI strings and path helpers live in [src/i18n/ui.ts](src/i18n/ui.ts). `t(locale, key)` for translations; `localizePath(path, locale)` prefixes `/en` when needed; `switchLocalePath(pathname, target)` rewrites the current URL to the other locale (used by the header language switcher and hreflang alternates in [BaseHead.astro](src/components/BaseHead.astro)). - Site titles/descriptions per locale live in [src/consts.ts](src/consts.ts) (`SITE.de`, `SITE.en`). The `SITE[locale]` map is the single source of truth — update when rebranding. -- Pages: German under `src/pages/` (`index.astro`, `about.astro`, `[...slug].astro`, `rss.xml.js`), English mirrors under `src/pages/en/`. The shared home UI lives in [src/components/HomePage.astro](src/components/HomePage.astro); both `index.astro` files are thin wrappers that pass `locale="de"` / `locale="en"`. +- Pages: German under `src/pages/` (`index.astro`, `about.astro`, `[...slug].astro`, `rss.xml.js`), English mirrors under `src/pages/en/`. The shared home UI lives in [src/components/PostsList.astro](src/components/PostsList.astro); both `index.astro` files are thin wrappers that pass `locale="de"` / `locale="en"`. - Layouts: [BaseLayout.astro](src/layouts/BaseLayout.astro) is the common skeleton (`` / `
` with `BaseHead` / `` with `Header` + `main` + `Footer`). Accepts `title`, `description`, `locale`, optional `image`, optional `entry` (for the language-switch translation lookup), optional `bodyClass`, and a `head` named slot for per-page ``/`` extras. All page templates compose via this layout — don't re-assemble head/header/footer by hand. [Post.astro](src/layouts/Post.astro) wraps `BaseLayout` to add hero image + title block + category line for single posts. `Header`, `BaseHead`, and `FormattedDate` also accept `locale` directly and fall back to `getLocaleFromUrl(Astro.url)`. - Separate RSS feeds per locale: `/rss.xml` (de) and `/en/rss.xml`. The sitemap integration is configured with `i18n: { defaultLocale: 'de', locales: { de: 'de-DE', en: 'en-US' } }`. diff --git a/src/components/PostsList.astro b/src/components/PostsList.astro index 6cb3e9e..308ddbc 100644 --- a/src/components/PostsList.astro +++ b/src/components/PostsList.astro @@ -3,24 +3,64 @@ import { Image } from 'astro:assets'; import FormattedDate from '~/components/FormattedDate.astro'; import BaseLayout from '~/layouts/BaseLayout.astro'; import { type Locale, SITE } from '~/consts'; -import { getPostsByLocale, postSlug } from '~/i18n/posts'; -import { localizePath } from '~/i18n/ui'; +import { + categoryHref, + getCategoriesByLocale, + getPostsByCategory, + getPostsByLocale, + paginationSegment, + POSTS_PER_PAGE, + postSlug, +} from '~/i18n/posts'; +import { localizePath, t } from '~/i18n/ui'; interface Props { locale: Locale; + page?: number; } -const { locale } = Astro.props; -const posts = await getPostsByLocale(locale); +const { locale, page = 1 } = Astro.props; +const all = await getPostsByLocale(locale); +const categories = await getCategoriesByLocale(locale); +const categoriesWithCounts = ( + await Promise.all( + categories.map(async (c) => ({ + category: c, + count: (await getPostsByCategory(c)).length, + })), + ) +).filter((c) => c.count > 0); +const totalPages = Math.max(1, Math.ceil(all.length / POSTS_PER_PAGE)); +const currentPage = Math.min(Math.max(1, page), totalPages); +const posts = all.slice((currentPage - 1) * POSTS_PER_PAGE, currentPage * POSTS_PER_PAGE); + +const segment = paginationSegment(locale); +const homeHref = localizePath('/', locale); +const newerHref = + currentPage === 2 + ? homeHref + : currentPage > 2 + ? localizePath(`/${segment}/${currentPage - 1}/`, locale) + : undefined; +const olderHref = + currentPage < totalPages + ? localizePath(`/${segment}/${currentPage + 1}/`, locale) + : undefined; +const pageLabel = t(locale, 'pagination.page') + .replace('{current}', String(currentPage)) + .replace('{total}', String(totalPages)); +const pageTitle = + currentPage === 1 ? SITE[locale].title : `${SITE[locale].title} – ${pageLabel}`; ---
-