Skip to content
Merged
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
4 changes: 2 additions & 2 deletions locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -575,10 +575,10 @@
"german": "Deutsch"
},
"updates": {
"metaTitle": "Monatliche Updates | Open Elements",
"metaTitle": "Monatliche Updates zu {project} | Open Elements",
"metaDescription": "Verfolgen Sie unseren monatlichen Fortschritt bei Open-Source-Projekten β€” Funktionen, Fixes und Community-BeitrΓ€ge.",
"title": "Monatliche Updates",
"description": "Ein transparenter Blick auf unsere monatliche Arbeit an Open-Source-Projekten",
"description": "Ein transparenter Blick auf unsere monatliche Arbeit an {project}",
"latest": "Aktuell",
"contributors": "Mitwirkende",
"seeMore": "Mehr anzeigen",
Expand Down
4 changes: 2 additions & 2 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -527,10 +527,10 @@
}
},
"updates": {
"metaTitle": "Monthly Updates | Open Elements",
"metaTitle": "{project} Monthly Updates | Open Elements",
"metaDescription": "Follow our monthly progress on open-source projects β€” features, fixes, and community contributions.",
"title": "Monthly Updates",
"description": "A transparent look at our monthly work on open-source projects",
"description": "A transparent look at our monthly work on {project}",
"latest": "Latest",
"contributors": "Contributors",
"seeMore": "See More",
Expand Down
3 changes: 2 additions & 1 deletion src/app/[locale]/sitemap.xml/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ function getStaticRoutes(locale: string): string[] {
'posts',
'support-care',
'support-care-maven',
'updates',
'updates/maven',
'updates/junit',
];

if (locale === 'de') {
Expand Down
43 changes: 43 additions & 0 deletions src/app/[locale]/updates/[project]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { notFound } from 'next/navigation';
import { getAllUpdates } from '@/lib/updates';
import { PROJECTS, getProject } from '@/lib/projects';
import { getTranslations } from 'next-intl/server';
import UpdatesClient from '@/components/UpdatesClient';

export function generateStaticParams() {
return PROJECTS.map(({ project }) => ({ project }));
}

export async function generateMetadata({
params,
}: {
params: Promise<{ locale: string; project: string }>;
}) {
const { locale, project } = await params;
const name = getProject(project)?.name;
const t = await getTranslations({ locale, namespace: 'updates' });
const title = t('metaTitle', { project: name ?? '' });

return {
title,
description: t('metaDescription'),
openGraph: {
title,
description: t('metaDescription'),
type: 'website',
},
};
}

export default async function UpdatesPage({
params,
}: {
params: Promise<{ locale: string; project: string }>;
}) {
const { locale, project } = await params;
if (!getProject(project)) notFound();

const updates = getAllUpdates(locale, project);

return <UpdatesClient updates={updates} project={project} />;
}
30 changes: 4 additions & 26 deletions src/app/[locale]/updates/page.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,11 @@
import { getAllUpdates } from '@/lib/updates';
import { getTranslations } from 'next-intl/server';
import UpdatesClient from '@/components/UpdatesClient';
import { redirect } from '@/i18n/routing';
import { PROJECTS } from '@/lib/projects';

export async function generateMetadata({
export default async function UpdatesIndex({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'updates' });

return {
title: t('metaTitle'),
description: t('metaDescription'),
openGraph: {
title: t('metaTitle'),
description: t('metaDescription'),
type: 'website',
},
};
}

export default async function UpdatesPage({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
const updates = getAllUpdates(locale);

return <UpdatesClient updates={updates} />;
redirect({ href: `/updates/${PROJECTS[0].project}`, locale });
}
70 changes: 66 additions & 4 deletions src/components/UpdatesClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,63 @@
import { useState } from 'react';
import Image from 'next/image';
import type { MonthlyUpdate, UpdateCategory, ItemType } from '@/types/updates';
import { useTranslations } from 'next-intl';
import { useTranslations, useLocale } from 'next-intl';
import { Link } from '@/i18n/routing';
import { PROJECTS, getProject } from '@/lib/projects';

const MONTHS = [
'JANUARY',
'FEBRUARY',
'MARCH',
'APRIL',
'MAY',
'JUNE',
'JULY',
'AUGUST',
'SEPTEMBER',
'OCTOBER',
'NOVEMBER',
'DECEMBER',
];

// Data stores months as uppercase English (e.g. "APRIL"); render them in the
// active locale, keeping the existing all-caps styling.
function localizeMonth(month: string, locale: string): string {
const idx = MONTHS.indexOf(month.toUpperCase());
if (idx < 0) return month;
return new Intl.DateTimeFormat(locale, { month: 'long' })
.format(new Date(Date.UTC(2000, idx, 1)))
.toUpperCase();
}

function ProjectNav({ active }: { active: string }) {
return (
<nav className="flex flex-wrap items-center justify-center gap-3 mt-8">
{PROJECTS.map(({ project, name, logo }) => {
const isActive = project === active;
return (
<Link
key={project}
href={`/updates/${project}`}
aria-current={isActive ? 'page' : undefined}
className={`flex items-center rounded-full border px-5 py-2.5 transition-colors ${
isActive
? 'border-green bg-green-100'
: 'border-slate opacity-60 hover:opacity-100 hover:border-green/40'
}`}>
<Image
src={logo}
alt={name}
width={120}
height={32}
className="h-8 w-auto shrink-0"
/>
</Link>
);
})}
</nav>
);
}

const ITEM_TYPE_CONFIG: Record<
ItemType,
Expand Down Expand Up @@ -141,6 +197,8 @@ function UpdateCard({
isLast: boolean;
}) {
const t = useTranslations('updates');
const locale = useLocale();
const month = localizeMonth(update.month, locale);

return (
<li className="relative flex md:gap-x-9 sm:gap-x-5 gap-x-3">
Expand All @@ -155,7 +213,7 @@ function UpdateCard({

{/* Month label (desktop) */}
<p className="-top-3 font-semibold absolute lg:block hidden -left-16 text-xs uppercase tracking-wider text-green-300">
{update.month}
{month}
</p>

{/* Timeline dot */}
Expand All @@ -170,7 +228,7 @@ function UpdateCard({
{/* Header */}
<div className="flex flex-wrap items-baseline gap-x-3 gap-y-1">
<h3 className="font-light text-2xl sm:text-3xl">
{update.month} {update.year}
{month} {update.year}
</h3>
{isFirst && (
<span className="text-xs bg-green-100 text-green-300 font-bold px-3 py-1 rounded-full">
Expand Down Expand Up @@ -207,10 +265,13 @@ function UpdateCard({

export default function UpdatesClient({
updates,
project,
}: {
updates: MonthlyUpdate[];
project: string;
}) {
const t = useTranslations('updates');
const projectName = getProject(project)?.name ?? project;

return (
<div className="relative bg-white">
Expand Down Expand Up @@ -241,7 +302,7 @@ export default function UpdatesClient({
<div className="relative flex flex-col items-center justify-center w-full">
<h1 className="text-center h1">{t('title')}</h1>
<p className="max-w-3xl mx-auto text-center text-base">
{t('description')}
{t('description', { project: projectName })}
</p>
<Image
src="/illustrations/line-p.svg"
Expand All @@ -252,6 +313,7 @@ export default function UpdatesClient({
/>
</div>
</div>
<ProjectNav active={project} />
</div>

<ul
Expand Down
Loading
Loading