From 0c530970af6774f49767fdb2978ca3e7cc035f47 Mon Sep 17 00:00:00 2001 From: chickenbreeder Date: Wed, 27 May 2026 14:09:11 +0200 Subject: [PATCH 1/5] Support listing of blog posts with specific tag --- src/components/Tag.astro | 35 ++++++++++++++++++ src/pages/[lang]/blog/[...slug].astro | 3 +- src/pages/[lang]/blog/tags/[tag].astro | 50 ++++++++++++++++++++++++++ src/pages/[lang]/blog/tags/index.astro | 46 ++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 src/components/Tag.astro create mode 100644 src/pages/[lang]/blog/tags/[tag].astro create mode 100644 src/pages/[lang]/blog/tags/index.astro diff --git a/src/components/Tag.astro b/src/components/Tag.astro new file mode 100644 index 00000000..fc5d96be --- /dev/null +++ b/src/components/Tag.astro @@ -0,0 +1,35 @@ +--- +import { useI18n } from "../i18n/utils"; + +interface Props { + name: string; +} + +const { translatePath } = useI18n(Astro.url); + +const { name } = Astro.props; +--- + + + {name} + + + + diff --git a/src/pages/[lang]/blog/[...slug].astro b/src/pages/[lang]/blog/[...slug].astro index 86037596..ea0bf577 100644 --- a/src/pages/[lang]/blog/[...slug].astro +++ b/src/pages/[lang]/blog/[...slug].astro @@ -5,6 +5,7 @@ import ContentDetailPage from "../../../layouts/ContentDetailPage.astro"; import { getCollectionStaticPaths } from "../../../lib/paths"; import { useI18n } from "../../../i18n/utils"; import { formatDate } from "../../../lib/formatters"; +import Tag from "../../../components/Tag.astro"; export async function getStaticPaths() { return getCollectionStaticPaths("blog"); @@ -61,7 +62,7 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString(); }
- {post.data.tags.map((tag: string) => {tag})} + {post.data.tags.map((tag: string) => )}
diff --git a/src/pages/[lang]/blog/tags/[tag].astro b/src/pages/[lang]/blog/tags/[tag].astro new file mode 100644 index 00000000..33852a0c --- /dev/null +++ b/src/pages/[lang]/blog/tags/[tag].astro @@ -0,0 +1,50 @@ +--- +import type { GetStaticPaths } from "astro"; +import { getCollection } from "astro:content"; +import CollectionPage from "../../../../layouts/CollectionPage.astro"; +import BlogCard from "../../../../components/BlogCard.astro"; +import { useI18n } from "../../../../i18n/utils"; +import { languages } from "../../../../i18n/ui"; +import { sortByDateDesc } from "../../../../lib/formatters"; + +export const getStaticPaths = (async () => { + const allPosts = await getCollection("blog"); + + const uniqueTags = [ + ...new Set(allPosts.flatMap((post) => post.data.tags || [])), + ]; + + return uniqueTags.flatMap((tag) => + Object.keys(languages).map((lang) => { + const filteredPosts = allPosts.filter((post) => + post.data.tags?.includes(tag), + ); + const sortedPosts = sortByDateDesc(filteredPosts); + + return { + params: { lang, tag }, + props: { posts: sortedPosts }, + }; + }), + ); +}) satisfies GetStaticPaths; + +const { lang } = useI18n(Astro.url); +const { tag } = Astro.params; +const { posts } = Astro.props; + +const title = "Blog"; +const description = `Tagged "${tag}"`; +--- + + + {posts.map(({ id, data }) => )} + diff --git a/src/pages/[lang]/blog/tags/index.astro b/src/pages/[lang]/blog/tags/index.astro new file mode 100644 index 00000000..51963324 --- /dev/null +++ b/src/pages/[lang]/blog/tags/index.astro @@ -0,0 +1,46 @@ +--- +import { getCollection } from "astro:content"; +import { useI18n } from "../../../../i18n/utils"; +import ContentDetailPage from "../../../../layouts/ContentDetailPage.astro"; +import { getLanguagePaths } from "../../../../lib/paths"; +import Tag from "../../../../components/Tag.astro"; + +export function getStaticPaths() { + return getLanguagePaths(); +} + +const { lang, translatePath } = useI18n(Astro.url); +const blogPosts = await getCollection("blog"); + +const tagCounts = blogPosts + .flatMap((post: any) => post.data.tags) + .reduce((acc: Record, tag: string) => { + acc[tag] = (acc[tag] || 0) + 1; + return acc; + }, {}); + +const tags = Object.entries(tagCounts).sort(([tagA], [tagB]) => + tagA.localeCompare(tagB), +); +--- + + +
+ {tags.map(([name, _]) => )} +
+
+ + From 0d5590e676a596182ffd8528ba5e05128ea9ffb5 Mon Sep 17 00:00:00 2001 From: chickenbreeder Date: Thu, 28 May 2026 11:25:17 +0200 Subject: [PATCH 2/5] Refactoring/clean up and translation of labels --- src/components/BackLink.astro | 34 +++++++++++++++++++++ src/components/Tag.astro | 15 +++++---- src/i18n/ch.ts | 4 +++ src/i18n/de.ts | 4 +++ src/i18n/en.ts | 4 +++ src/layouts/CollectionPage.astro | 21 +++++++++++-- src/layouts/ContentDetailPage.astro | 15 ++------- src/pages/[lang]/about/jobs/[...slug].astro | 4 +-- src/pages/[lang]/blog/[...slug].astro | 13 ++------ src/pages/[lang]/blog/tags/[tag].astro | 8 ++--- src/pages/[lang]/blog/tags/index.astro | 4 +-- src/pages/[lang]/customers/[...slug].astro | 2 +- src/pages/[lang]/services/[...slug].astro | 20 +++--------- src/styles/global.css | 2 ++ 14 files changed, 94 insertions(+), 56 deletions(-) create mode 100644 src/components/BackLink.astro diff --git a/src/components/BackLink.astro b/src/components/BackLink.astro new file mode 100644 index 00000000..5da991ad --- /dev/null +++ b/src/components/BackLink.astro @@ -0,0 +1,34 @@ +--- +interface Props { + href: string; + text: string; +} + +const { href, text } = Astro.props; +--- + +{text} + + diff --git a/src/components/Tag.astro b/src/components/Tag.astro index fc5d96be..c0dbbf36 100644 --- a/src/components/Tag.astro +++ b/src/components/Tag.astro @@ -10,26 +10,29 @@ const { translatePath } = useI18n(Astro.url); const { name } = Astro.props; --- - + {name} - diff --git a/src/i18n/ch.ts b/src/i18n/ch.ts index 278c674f..83614b7a 100644 --- a/src/i18n/ch.ts +++ b/src/i18n/ch.ts @@ -6,6 +6,10 @@ const ts = { "nav.contact": "Kontakt", "nav.blog": "Blog", "nav.language": "Sprach", + "nav.backToBlog": "Zrügg zum Blog", + "nav.backToServices": "Zrügg zu Services", + "nav.backToJobs": "Zrügg zu Jobs", + "nav.backToCustomers": "Zrügg zu Chunde", // Buttons & CTAs "cta.freeWorkshop": "Gratis Cloud Readiness Workshop", diff --git a/src/i18n/de.ts b/src/i18n/de.ts index 6e4ac148..05cedb01 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -6,6 +6,10 @@ const de = { "nav.contact": "Kontakt", "nav.blog": "Blog", "nav.language": "Sprache", + "nav.backToBlog": "Zurück zum Blog", + "nav.backToServices": "Zurück zu Services", + "nav.backToJobs": "Zurück zu Jobs", + "nav.backToCustomers": "Zurück zu Kunden", // Buttons & CTAs "cta.freeWorkshop": "Kostenloser Cloud Readiness Workshop", diff --git a/src/i18n/en.ts b/src/i18n/en.ts index bb8cd814..2ff13f86 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -6,6 +6,10 @@ const en = { "nav.contact": "Contact", "nav.blog": "Blog", "nav.language": "Language", + "nav.backToBlog": "Back to Blog", + "nav.backToServices": "Back to Services", + "nav.backToJobs": "Back to Jobs", + "nav.backToCustomers": "Back to Customers", // Buttons & CTAs "cta.freeWorkshop": "Free Cloud Readiness Workshop", diff --git a/src/layouts/CollectionPage.astro b/src/layouts/CollectionPage.astro index 801d3be4..4ea71cc0 100644 --- a/src/layouts/CollectionPage.astro +++ b/src/layouts/CollectionPage.astro @@ -2,6 +2,7 @@ import Layout from "./Layout.astro"; import SEO from "../components/SEO.astro"; import Title from "../components/Title.astro"; +import BackLink from "../components/BackLink.astro"; interface Props { pageTitle: string; @@ -11,10 +12,19 @@ interface Props { lang: string; isEmpty?: boolean; emptyMessage?: string; + backLink?: { href: string; text: string }; } -const { pageTitle, title, subtitle, description, lang, isEmpty, emptyMessage } = - Astro.props; +const { + pageTitle, + title, + subtitle, + description, + lang, + isEmpty, + emptyMessage, + backLink, +} = Astro.props; --- @@ -22,6 +32,13 @@ const { pageTitle, title, subtitle, description, lang, isEmpty, emptyMessage } = <main class="collection-page"> <div class="container"> + { + backLink && ( + <div style="margin-bottom: 1em"> + <BackLink href={backLink.href} text={backLink.text} /> + </div> + ) + } <div class="collection-grid"> <slot /> </div> diff --git a/src/layouts/ContentDetailPage.astro b/src/layouts/ContentDetailPage.astro index 415f2b5a..5a326b09 100644 --- a/src/layouts/ContentDetailPage.astro +++ b/src/layouts/ContentDetailPage.astro @@ -2,6 +2,7 @@ import Layout from "./Layout.astro"; import SEO from "../components/SEO.astro"; import StructuredData from "../components/StructuredData.astro"; +import BackLink from "../components/BackLink.astro"; interface MetaItem { label: string; @@ -44,7 +45,7 @@ const { <main class="content-detail"> <article class="container"> <header class="content-header"> - <a href={backLink.href} class="back-link">{backLink.text}</a> + <BackLink href={backLink.href} text={backLink.text} /> <slot name="header-before" /> <h1>{title}</h1> { @@ -92,18 +93,6 @@ const { margin-bottom: 3rem; } - .back-link { - display: inline-block; - color: var(--color-primary); - text-decoration: none; - margin-bottom: var(--spacing-md); - font-weight: 500; - } - - .back-link:hover { - text-decoration: underline; - } - h1 { font-size: 2.5rem; line-height: 1.2; diff --git a/src/pages/[lang]/about/jobs/[...slug].astro b/src/pages/[lang]/about/jobs/[...slug].astro index 48d0159c..ef497718 100644 --- a/src/pages/[lang]/about/jobs/[...slug].astro +++ b/src/pages/[lang]/about/jobs/[...slug].astro @@ -11,7 +11,7 @@ export async function getStaticPaths() { const { job } = Astro.props; const { Content } = await render(job); -const { lang, translatePath } = useI18n(Astro.url); +const { lang, translatePath, t } = useI18n(Astro.url); const title = `${job.data.title} | Jobs`; const description = `Join bespinian as a ${job.data.title}. ${job.data.location ? "Location: " + job.data.location + "." : ""} ${job.data.employment ? job.data.employment + " position." : ""} We're looking for talented individuals to help companies succeed with cloud-native technologies.`; @@ -29,7 +29,7 @@ if (job.data.employment) metaItems.push({ label: job.data.employment }); {lang} backLink={{ href: translatePath("/about/jobs"), - text: "← Back to Jobs", + text: t("nav.backToJobs"), }} meta={metaItems} structuredData={{ diff --git a/src/pages/[lang]/blog/[...slug].astro b/src/pages/[lang]/blog/[...slug].astro index ea0bf577..d291a37a 100644 --- a/src/pages/[lang]/blog/[...slug].astro +++ b/src/pages/[lang]/blog/[...slug].astro @@ -14,7 +14,7 @@ export async function getStaticPaths() { const { post } = Astro.props; const { Content } = await render(post); -const { lang, translatePath } = useI18n(Astro.url); +const { lang, translatePath, t } = useI18n(Astro.url); const formattedDate = formatDate(post.data.pubDate, lang); @@ -29,7 +29,7 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString(); title={post.data.title} {description} {lang} - backLink={{ href: translatePath("/blog"), text: "← Back to Blog" }} + backLink={{ href: translatePath("/blog"), text: t("nav.backToBlog") }} meta={[ { label: post.data.author }, { label: formattedDate, datetime: post.data.pubDate.toISOString() }, @@ -87,13 +87,4 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString(); flex-wrap: wrap; gap: 0.5rem; } - - .tag { - background: #f0f0f0; - color: var(--color-gray); - padding: 0.25rem 0.75rem; - border-radius: 12px; - font-size: 0.875rem; - font-weight: 500; - } </style> diff --git a/src/pages/[lang]/blog/tags/[tag].astro b/src/pages/[lang]/blog/tags/[tag].astro index 33852a0c..228737db 100644 --- a/src/pages/[lang]/blog/tags/[tag].astro +++ b/src/pages/[lang]/blog/tags/[tag].astro @@ -29,22 +29,22 @@ export const getStaticPaths = (async () => { ); }) satisfies GetStaticPaths; -const { lang } = useI18n(Astro.url); +const { lang, translatePath, t } = useI18n(Astro.url); const { tag } = Astro.params; const { posts } = Astro.props; -const title = "Blog"; +const title = `Blog | ${tag}`; const description = `Tagged "${tag}"`; --- <CollectionPage pageTitle={title} - {title} + title="Blog" subtitle={description} {description} {lang} isEmpty={posts.length === 0} - emptyMessage={`No posts found with tag "${tag}"`} + backLink={{ href: translatePath("/blog"), text: t("nav.backToBlog") }} > {posts.map(({ id, data }) => <BlogCard {id} {...data} />)} </CollectionPage> diff --git a/src/pages/[lang]/blog/tags/index.astro b/src/pages/[lang]/blog/tags/index.astro index 51963324..5719a768 100644 --- a/src/pages/[lang]/blog/tags/index.astro +++ b/src/pages/[lang]/blog/tags/index.astro @@ -9,7 +9,7 @@ export function getStaticPaths() { return getLanguagePaths(); } -const { lang, translatePath } = useI18n(Astro.url); +const { lang, translatePath, t } = useI18n(Astro.url); const blogPosts = await getCollection("blog"); const tagCounts = blogPosts @@ -29,7 +29,7 @@ const tags = Object.entries(tagCounts).sort(([tagA], [tagB]) => lang={lang} title="Tags" description="The blog tags" - backLink={{ href: translatePath("/blog"), text: "← Back to Blog" }} + backLink={{ href: translatePath("/blog"), text: t("nav.backToBlog") }} > <div class="tags"> {tags.map(([name, _]) => <Tag {name} />)} diff --git a/src/pages/[lang]/customers/[...slug].astro b/src/pages/[lang]/customers/[...slug].astro index d2ddb11f..92e2e9fa 100644 --- a/src/pages/[lang]/customers/[...slug].astro +++ b/src/pages/[lang]/customers/[...slug].astro @@ -29,7 +29,7 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString(); {lang} backLink={{ href: translatePath("/customers"), - text: "← Back to Customers", + text: t("nav.backToCustomers"), }} meta={[ { label: customer.data.company }, diff --git a/src/pages/[lang]/services/[...slug].astro b/src/pages/[lang]/services/[...slug].astro index 8928f2cc..6708fc2c 100644 --- a/src/pages/[lang]/services/[...slug].astro +++ b/src/pages/[lang]/services/[...slug].astro @@ -8,6 +8,7 @@ import CTA from "../../../components/CTA.astro"; import { languages } from "../../../i18n/ui"; import { useI18n } from "../../../i18n/utils"; import spaceship from "../../../assets/spaceship1.svg"; +import BackLink from "../../../components/BackLink.astro"; export async function getStaticPaths() { const allServices = await getCollection("services"); @@ -54,9 +55,10 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString(); <article> <div class="container"> <header class="service-header"> - <a href={translatePath("/#services")} class="back-link"> - ← Back to Services - </a> + <BackLink + href={translatePath("/#services")} + text={t("nav.backToServices")} + /> </header> </div> @@ -146,18 +148,6 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString(); margin-bottom: 3rem; } - .back-link { - display: inline-block; - color: var(--color-primary); - text-decoration: none; - margin-bottom: var(--spacing-md); - font-weight: 500; - } - - .back-link:hover { - text-decoration: underline; - } - h1 { font-size: 3.5rem; line-height: 1.2; diff --git a/src/styles/global.css b/src/styles/global.css index d49e75cb..81f4342b 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -12,6 +12,8 @@ --color-background-gray: #efefef; --color-blockquote-bg: #f9f9f9; + --color-gray-light: #f0f0f0; + /* Spacing */ --spacing-xs: 0.5rem; --spacing-sm: 1rem; From 53f32e7e7e76c36d9712e5478fddf527eb1165a1 Mon Sep 17 00:00:00 2001 From: chickenbreeder <chickenbreeder@users.noreply.github.com> Date: Thu, 28 May 2026 13:43:57 +0200 Subject: [PATCH 3/5] Add navigation elements at the end of posts --- src/i18n/ch.ts | 1 + src/i18n/de.ts | 1 + src/i18n/en.ts | 1 + src/pages/[lang]/blog/[...slug].astro | 40 ++++++++++++++++++++++++++ src/pages/[lang]/blog/tags/[tag].astro | 2 +- 5 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/i18n/ch.ts b/src/i18n/ch.ts index 83614b7a..663ea59c 100644 --- a/src/i18n/ch.ts +++ b/src/i18n/ch.ts @@ -87,6 +87,7 @@ const ts = { "blog.title": "Blog", "blog.subtitle": "Tipps, Aleitige u Best Practices für modärni Cloud-Büetz", "blog.empty": "No keni Blog-Artikel verfüegbar. Lueg gly mau wieder ine!", + "blog.allPosts": "Alle Artikel", // Customers Page "customers.page.title": "Üsi Chunde", diff --git a/src/i18n/de.ts b/src/i18n/de.ts index 05cedb01..48365c44 100644 --- a/src/i18n/de.ts +++ b/src/i18n/de.ts @@ -89,6 +89,7 @@ const de = { "blog.subtitle": "Tipps, Anleitungen und Best Practices für moderne Cloud-Entwicklung", "blog.empty": "Noch keine Blog-Artikel verfügbar. Schau bald wieder vorbei!", + "blog.allPosts": "Alle Artikel", // Customers Page "customers.page.title": "Unsere Kunden", diff --git a/src/i18n/en.ts b/src/i18n/en.ts index 2ff13f86..99dceb56 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -87,6 +87,7 @@ const en = { "blog.title": "Blog", "blog.subtitle": "Tips, guides, and best practices to work with cloud tech", "blog.empty": "No blog posts yet. Check back soon!", + "blog.allPosts": "All Posts", // Customers Page "customers.page.title": "Our Customers", diff --git a/src/pages/[lang]/blog/[...slug].astro b/src/pages/[lang]/blog/[...slug].astro index d291a37a..a3533f77 100644 --- a/src/pages/[lang]/blog/[...slug].astro +++ b/src/pages/[lang]/blog/[...slug].astro @@ -6,6 +6,7 @@ import { getCollectionStaticPaths } from "../../../lib/paths"; import { useI18n } from "../../../i18n/utils"; import { formatDate } from "../../../lib/formatters"; import Tag from "../../../components/Tag.astro"; +import rss from "../../../assets/rss.svg"; export async function getStaticPaths() { return getCollectionStaticPaths("blog"); @@ -66,6 +67,13 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString(); </div> <Content /> + <div class="actions"> + <a href={translatePath("/blog")} class="btn">{t("blog.allPosts")}</a> + <a href="/rss.xml" class="btn rss"> + <img width="10" src={rss.src} alt="RSS Feed" /> + RSS + </a> + </div> </ContentDetailPage> <style> @@ -87,4 +95,36 @@ const canonicalUrl = new URL(Astro.url.pathname, Astro.site).toString(); flex-wrap: wrap; gap: 0.5rem; } + + .actions { + margin: 5em 0; + padding-top: 2em; + border-top: 1px solid #cecece; + display: flex; + gap: 1rem; + } + + .btn { + display: inline-flex; + text-decoration: none !important; + color: var(--color-primary); + padding: 6px 16px; + border: 1px solid var(--color-background-primary); + background-color: var(--color-background-secondary); + border-radius: 6px; + font-size: 0.875rem; + } + + .btn:hover { + background-color: var(--color-background-primary); + color: var(--color-secondary); + } + + .btn:hover > img { + filter: invert(1); + } + + .btn img { + margin: 0 0.25rem; + } </style> diff --git a/src/pages/[lang]/blog/tags/[tag].astro b/src/pages/[lang]/blog/tags/[tag].astro index 228737db..773fdbd6 100644 --- a/src/pages/[lang]/blog/tags/[tag].astro +++ b/src/pages/[lang]/blog/tags/[tag].astro @@ -39,7 +39,7 @@ const description = `Tagged "${tag}"`; <CollectionPage pageTitle={title} - title="Blog" + title={t("blog.title")} subtitle={description} {description} {lang} From ec7dcbb8a696db7e0607f52cc89aa1164061a968 Mon Sep 17 00:00:00 2001 From: chickenbreeder <chickenbreeder@users.noreply.github.com> Date: Fri, 29 May 2026 13:59:19 +0200 Subject: [PATCH 4/5] simplify CSS --- src/components/Tag.astro | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/components/Tag.astro b/src/components/Tag.astro index c0dbbf36..d1f14ca2 100644 --- a/src/components/Tag.astro +++ b/src/components/Tag.astro @@ -5,25 +5,18 @@ interface Props { name: string; } -const { translatePath } = useI18n(Astro.url); - const { name } = Astro.props; + +const { translatePath } = useI18n(Astro.url); --- <a class="tag" href={translatePath(`/blog/tags/${encodeURIComponent(name)}`)}> - <span>{name}</span> + {name} </a> <style> .tag { text-decoration: none; - } - - .tag:visited { - text-decoration: none; - } - - .tag > span { background: var(--color-gray-light); color: var(--color-gray); padding: 0.25rem 0.75rem; @@ -32,7 +25,11 @@ const { name } = Astro.props; font-weight: 500; } - .tag > span:hover { + .tag:visited { + text-decoration: none; + } + + .tag:hover { background-color: #cecece; } </style> From 407635222c092d03ddd429e9a156965765e02cf2 Mon Sep 17 00:00:00 2001 From: chickenbreeder <chickenbreeder@users.noreply.github.com> Date: Fri, 29 May 2026 14:08:34 +0200 Subject: [PATCH 5/5] be overly specific again to fix small issue --- src/components/Tag.astro | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Tag.astro b/src/components/Tag.astro index d1f14ca2..4dcb9f40 100644 --- a/src/components/Tag.astro +++ b/src/components/Tag.astro @@ -15,7 +15,7 @@ const { translatePath } = useI18n(Astro.url); </a> <style> - .tag { + a.tag { text-decoration: none; background: var(--color-gray-light); color: var(--color-gray); @@ -25,11 +25,11 @@ const { translatePath } = useI18n(Astro.url); font-weight: 500; } - .tag:visited { + a.tag:visited { text-decoration: none; } - .tag:hover { + a.tag:hover { background-color: #cecece; } </style>