Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/components/LanguageToggle.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
---
import { languages } from "@/i18n";
import GlobeIcon from "@/components/icons/globe.svg?raw";
---

<div class="relative ms-2 sm:ms-4">
<button
id="language-toggle"
class="hover:text-accent flex h-9 w-9 cursor-pointer items-center justify-center rounded-md"
type="button"
aria-label="Select language"
aria-expanded="false"
aria-haspopup="true"
>
<Fragment set:html={GlobeIcon} />
</button>
<div
id="language-menu"
class="bg-global-bg border-accent absolute right-0 top-full z-50 mt-1 hidden min-w-[120px] rounded-md border py-1 shadow-lg"
role="menu"
aria-orientation="vertical"
>
{
Object.entries(languages).map(([code, label]) => (
<button
type="button"
data-lang={code}
class="lang-option block w-full cursor-pointer px-4 py-2 text-left text-sm hover:bg-zinc-100 dark:hover:bg-zinc-800"
role="menuitem"
>
{label}
</button>
))
}
</div>
</div>

<script>
import { getLang, setLang, initI18n, languages, type Lang } from "@/i18n/client";

function setupLanguageToggle() {
const toggle = document.getElementById("language-toggle");
const menu = document.getElementById("language-menu");
const langOptions = document.querySelectorAll(".lang-option");

if (!toggle || !menu) return;

// Initialize i18n and apply translations
initI18n();

// Mark current language as active
const currentLang = getLang();
langOptions.forEach((option) => {
const lang = (option as HTMLButtonElement).dataset.lang;
if (lang === currentLang) {
option.classList.add("text-accent", "font-semibold");
} else {
option.classList.remove("text-accent", "font-semibold");
}
});

// Toggle menu visibility
toggle.addEventListener("click", (e) => {
e.stopPropagation();
const isExpanded = toggle.getAttribute("aria-expanded") === "true";
toggle.setAttribute("aria-expanded", String(!isExpanded));
menu.classList.toggle("hidden");
});

// Handle language selection
langOptions.forEach((option) => {
option.addEventListener("click", () => {
const lang = (option as HTMLButtonElement).dataset.lang as Lang;
if (lang && lang in languages) {
setLang(lang);
// Reload to apply changes everywhere
window.location.reload();
}
});
});

// Close menu when clicking outside
document.addEventListener("click", (e) => {
if (!toggle.contains(e.target as Node) && !menu.contains(e.target as Node)) {
toggle.setAttribute("aria-expanded", "false");
menu.classList.add("hidden");
}
});

// Close menu on escape key
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") {
toggle.setAttribute("aria-expanded", "false");
menu.classList.add("hidden");
}
});
}

// Run on page load
setupLanguageToggle();

// Run on Astro view transitions
document.addEventListener("astro:page-load", setupLanguageToggle);
</script>
5 changes: 3 additions & 2 deletions src/components/Paginator.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import type { PaginationLink } from "@/types";
import { t } from "@/i18n";

interface Props {
nextUrl?: PaginationLink;
Expand All @@ -15,13 +16,13 @@ const { nextUrl, prevUrl } = Astro.props;
{prevUrl && (
<a class="hover:text-accent me-auto py-2" href={prevUrl.url}>
{prevUrl.srLabel && <span class="sr-only">{prevUrl.srLabel}</span>}
{prevUrl.text ? prevUrl.text : "Previous"}
{prevUrl.text ? prevUrl.text : t.pagination.previous}
</a>
)}
{nextUrl && (
<a class="hover:text-accent ms-auto py-2" href={nextUrl.url}>
{nextUrl.srLabel && <span class="sr-only">{nextUrl.srLabel}</span>}
{nextUrl.text ? nextUrl.text : "Next"}
{nextUrl.text ? nextUrl.text : t.pagination.next}
</a>
)}
</nav>
Expand Down
8 changes: 4 additions & 4 deletions src/components/Search.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Heavy inspiration taken from Astro Starlight -> https://github.com/withastro/starlight/blob/main/packages/starlight/components/Search.astro

import "@/styles/blocks/search.css";
import { t } from "@/i18n";
---

<site-search class="ms-auto" id="search">
Expand All @@ -27,7 +28,7 @@ import "@/styles/blocks/search.css";
<path d="M0 0h24v24H0z" stroke="none"></path>
<path d="M3 10a7 7 0 1 0 14 0 7 7 0 1 0-14 0M21 21l-6-6"></path>
</svg>
<span class="sr-only">Open Search</span>
<span class="sr-only">{t.search.openSearch}</span>
</button>
<dialog
aria-label="search"
Expand All @@ -36,14 +37,13 @@ import "@/styles/blocks/search.css";
<div class="dialog-frame flex grow flex-col gap-4 p-6 pt-12 sm:pt-6">
<button
class="ms-auto cursor-pointer rounded-md bg-zinc-200 p-2 font-semibold dark:bg-zinc-700"
data-close-modal>Close</button
data-close-modal>{t.search.close}</button
>
{
import.meta.env.DEV ? (
<div class="mx-auto text-center">
<p>
Search is only available in production builds. <br />
Try building and previewing the site to test it out locally.
{t.search.devWarning}
</p>
</div>
) : (
Expand Down
6 changes: 5 additions & 1 deletion src/components/SkipLink.astro
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
---
import { t } from "@/i18n";
---

<a class="sr-only focus:not-sr-only focus:fixed focus:start-1 focus:top-1.5" href="#main"
>skip to content
>{t.global.skipToContent}
</a>
3 changes: 2 additions & 1 deletion src/components/SocialList.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { Icon } from "astro-icon/components";
import { t } from "@/i18n";

/**
Uses https://www.astroicon.dev/getting-started/
Expand All @@ -21,7 +22,7 @@ const socialLinks: {
---

<div class="flex flex-wrap items-end gap-x-2">
<p>Find me on</p>
<p data-i18n="global.findMeOn">{t.global.findMeOn}</p>
<ul class="flex flex-1 items-center gap-x-2 sm:flex-initial">
{
socialLinks.map(({ friendlyName, isWebmention, link, name }) => (
Expand Down
6 changes: 5 additions & 1 deletion src/components/ThemeToggle.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
---
import { t } from "@/i18n";
---

<theme-toggle class="ms-2 sm:ms-4">
<button class="hover:text-accent relative h-9 w-9 cursor-pointer rounded-md p-2" type="button">
<span class="sr-only">Dark Theme</span>
<span class="sr-only">{t.theme.dark}</span>
<svg
aria-hidden="true"
class="absolute start-1/2 top-1/2 h-7 w-7 -translate-x-1/2 -translate-y-1/2 scale-100 opacity-100 transition-all dark:scale-0 dark:opacity-0"
Expand Down
7 changes: 4 additions & 3 deletions src/components/blog/Masthead.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Image } from "astro:assets";
import type { CollectionEntry } from "astro:content";
import FormattedDate from "@/components/FormattedDate.astro";
import { t } from "@/i18n";

interface Props {
content: CollectionEntry<"post">;
Expand Down Expand Up @@ -32,7 +33,7 @@ const dateTimeOptions: Intl.DateTimeFormatOptions = {
</div>
)
}
{data.draft ? <span class="text-base text-red-500">(Draft)</span> : null}
{data.draft ? <span class="text-base text-red-500">({t.blog.draft})</span> : null}
<h1 class="title">
{data.title}
</h1>
Expand All @@ -44,7 +45,7 @@ const dateTimeOptions: Intl.DateTimeFormatOptions = {
{
data.updatedDate && (
<span class="bg-quote/5 text-quote rounded-lg px-2 py-1">
Updated:
{t.blog.lastUpdated}:
<FormattedDate class="ms-1" date={data.updatedDate} dateTimeOptions={dateTimeOptions} />
</span>
)
Expand Down Expand Up @@ -74,7 +75,7 @@ const dateTimeOptions: Intl.DateTimeFormatOptions = {
<>
{/* prettier-ignore */}
<span class="contents">
<a class="cactus-link inline-block before:content-['#']" data-pagefind-filter={`tag:${tag}`} href={`/tags/${tag}/`}><span class="sr-only">View more blogs with the tag&nbsp;</span>{tag}
<a class="cactus-link inline-block before:content-['#']" data-pagefind-filter={`tag:${tag}`} href={`/tags/${tag}/`}><span class="sr-only">{t.blog.viewMoreWithTag}&nbsp;</span>{tag}
</a>{i < data.tags.length - 1 && ", "}
</span>
</>
Expand Down
3 changes: 2 additions & 1 deletion src/components/blog/PostPreview.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import type { CollectionEntry } from "astro:content";
import FormattedDate from "@/components/FormattedDate.astro";
import type { HTMLTag, Polymorphic } from "astro/types";
import { t } from "@/i18n";

type Props<Tag extends HTMLTag> = Polymorphic<{ as: Tag }> & {
post: CollectionEntry<"post">;
Expand All @@ -16,7 +17,7 @@ const { as: Tag = "div", post, withDesc = false } = Astro.props;
date={post.data.publishDate}
/>
<Tag>
{post.data.draft && <span class="text-red-500">(Draft) </span>}
{post.data.draft && <span class="text-red-500">({t.blog.draft}) </span>}
<a class="cactus-link" href={`/posts/${post.id}/`}>
{post.data.title}
</a>
Expand Down
3 changes: 2 additions & 1 deletion src/components/blog/TOC.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { generateToc } from "@/utils/generateToc";
import type { MarkdownHeading } from "astro";
import TOCHeading from "./TOCHeading.astro";
import { t } from "@/i18n";

interface Props {
headings: MarkdownHeading[];
Expand All @@ -13,7 +14,7 @@ const toc = generateToc(headings);
---

<details open class="lg:sticky lg:top-12 lg:order-2 lg:-me-32 lg:basis-64">
<summary class="title hover:marker:text-accent cursor-pointer text-lg">Table of Contents</summary>
<summary class="title hover:marker:text-accent cursor-pointer text-lg">{t.blog.tableOfContents}</summary>
<nav class="ms-4 lg:w-full">
<ol class="mt-4">
{toc.map((heading) => <TOCHeading heading={heading} />)}
Expand Down
7 changes: 4 additions & 3 deletions src/components/blog/webmentions/Comments.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { Image } from "astro:assets";
import type { WebmentionsChildren } from "@/types";
import { Icon } from "astro-icon/components";
import { t } from "@/i18n";

interface Props {
mentions: WebmentionsChildren[];
Expand All @@ -20,7 +21,7 @@ const comments = mentions.filter(
!!comments.length && (
<div>
<p class="text-accent-2 mb-0">
<strong>{comments.length}</strong> Mention{comments.length > 1 ? "s" : ""}
<strong>{comments.length}</strong> {comments.length > 1 ? t.webmentions.mentions : t.webmentions.mention}
</p>
<ul class="divide-global-text/20 mt-0 divide-y ps-0" role="list">
{comments.map((mention) => (
Expand Down Expand Up @@ -65,8 +66,8 @@ const comments = mentions.filter(
target="_blank"
>
<span class="hidden" id="cmt-source">
Visit the source of this webmention
</span>
{t.webmentions.visitSource}
</span>
<Icon
aria-hidden="true"
class="h-5 w-5"
Expand Down
3 changes: 2 additions & 1 deletion src/components/blog/webmentions/Likes.astro
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
---
import { Image } from "astro:assets";
import type { WebmentionsChildren } from "@/types";
import { t } from "@/i18n";

interface Props {
mentions: WebmentionsChildren[];
Expand All @@ -20,7 +21,7 @@ const likesToShow = likes
<div>
<p class="text-accent-2 mb-0">
<strong>{likes.length}</strong>
{likes.length > 1 ? " People" : " Person"} liked this
{likes.length > 1 ? ` ${t.webmentions.peopleLiked}` : ` ${t.webmentions.personLiked}`}
</p>
{!!likesToShow.length && (
<ul class="flex list-none flex-wrap overflow-hidden ps-2" role="list">
Expand Down
5 changes: 3 additions & 2 deletions src/components/blog/webmentions/index.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { getWebmentionsForUrl } from "@/utils/webmentions";
import Comments from "./Comments.astro";
import Likes from "./Likes.astro";
import { t } from "@/i18n";

const url = new URL(Astro.url.pathname, Astro.site);

Expand All @@ -12,12 +13,12 @@ if (!webMentions.length) return;
---

<hr class="border-solid" />
<h2 class="mb-8 before:hidden">Webmentions for this post</h2>
<h2 class="mb-8 before:hidden">{t.webmentions.webmentionsForPost}</h2>
<div class="space-y-10">
<Likes mentions={webMentions} />
<Comments mentions={webMentions} />
</div>
<p class="mt-8">
Responses powered by{" "}
{t.webmentions.responsesPoweredBy}{" "}
<a href="https://webmention.io" rel="noreferrer" target="_blank">Webmentions</a>
</p>
18 changes: 18 additions & 0 deletions src/components/icons/globe.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 7 additions & 2 deletions src/components/layout/Footer.astro
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
---
import { menuLinks, siteConfig } from "@/site.config";
import { t } from "@/i18n";

const year = new Date().getFullYear();
---
Expand All @@ -15,10 +16,14 @@ const year = new Date().getFullYear();
aria-labelledby="footer_links"
class="flex gap-x-2 sm:gap-x-0 sm:divide-x sm:divide-gray-500"
>
<p id="footer_links" class="sr-only">More on this site</p>
<p id="footer_links" class="sr-only" data-i18n="global.moreOnSite">{t.global.moreOnSite}</p>
{
menuLinks.map((link) => (
<a class="hover:text-global-text px-4 py-2 hover:underline sm:py-0" href={link.path}>
<a
class="hover:text-global-text px-4 py-2 hover:underline sm:py-0"
data-i18n={link.i18nKey}
href={link.path}
>
{link.title}
</a>
))
Expand Down
Loading