fix dynamic slug to editorials and news

This commit is contained in:
Esteban Paz 2026-06-30 20:36:42 -05:00
parent a22e86ef20
commit 8bae1f35e8
5 changed files with 79 additions and 275 deletions

View File

@ -38,6 +38,16 @@ export const routeTranslations = {
}
} as const;
export function getRouteKeyFromSlug(slug: string): keyof typeof routeTranslations {
for (const [key, translations] of Object.entries(routeTranslations)) {
const values = Object.values(translations) as string[];
if (values.includes(slug)) {
return key as keyof typeof routeTranslations;
}
}
return "news";
}
export function getLocalizedRoute(route: keyof typeof routeTranslations, locale: string | undefined): string {
const l = (locale in routeTranslations[route] ? locale : "es") as Locale;
return routeTranslations[route][l as keyof (typeof routeTranslations)[typeof route]] || routeTranslations[route]["en" as keyof (typeof routeTranslations)[typeof route]];

View File

@ -1,58 +0,0 @@
---
import MainLayout from "@/layouts/MainLayout.astro"
import Header from "@/components/Header.astro"
import NewsList from "@/components/cards/NewsList.astro";
import { getCollection } from "astro:content";
import FooterSection from "@/components/section/FooterSection.astro";
import { createTranslator, getLocalizedRoute, routeTranslations } from '@/i18n';
const tl = createTranslator(Astro.currentLocale);
export function getStaticPaths() {
const locales = Object.keys(routeTranslations.editorial);
return locales.map((locale) => ({
params: {
locale,
editorial_slug: getLocalizedRoute('editorial', locale)
},
}));
}
const { locale, editorial_slug } = Astro.params;
const items = await getCollection("editorial", (post)=>{
const currentLocale = Astro.currentLocale;
return post.data.locale == currentLocale
});
const sortedPosts = [...items]
.sort((a, b) => {
const dateDiff = new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
if (dateDiff !== 0) return dateDiff
return (a.data.order ?? 0) - (b.data.order ?? 0)
});
---
<MainLayout >
<div class="top-16 relative mb container mx-auto">
<Header />
</div>
<div class="container mx-auto mt-4">
<div class="flex flex-col lg:w-1/2 items-center mx-auto py-8">
<h1 class="text-white text-2xl uppercase font-bold text-center mb-4 font-primary md:mt-20 mt-10">{tl("editorial.title")}</h1>
<h2 class="text-white text-3xl lg:text-2xl font-bold text-center font-secondary mb-4 md:p-0 px-2">{tl("editorial.text")}</h2>
</div>
<div class="flex flex-col md:gap-0 gap-2 lg:max-w-4xl mx-auto bg-white p-4 md:p-8">
{
sortedPosts.map((item) => (
<div class="news-item">
<NewsList data={item} content={{ body: item.body }} routeKey="editorial" />
</div>
))
}
</div>
</div>
<FooterSection />
</MainLayout>

View File

@ -1,143 +0,0 @@
---
import { YouTube } from "astro-embed";
import MainLayout from "../../../layouts/MainLayout.astro";
import Header from "../../../components/Header.astro";
import CarouselSection from "../../../components/section/CarouselSection.astro";
import { Image } from "@unpic/astro";
import { getCollection, render } from "astro:content";
import TitleSection from "../../../components/section/TitleSection.astro";
import FooterSection from "../../../components/section/FooterSection.astro";
import { getLocalizedRoute } from "@/i18n";
export const prerender = true;
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
const posts = await getCollection("news");
return posts.map((post) => ({
params: {
id: post.id,
locale: post.data.locale,
news_slug: getLocalizedRoute("news", post.data.locale),
},
props: { post },
}));
}
const { locale, news_slug } = Astro.params;
// 2. For your template, you can get the entry directly from the prop
const { post } = Astro.props;
const { Content } = await render(post);
const baseSlug = news_slug;
const rawContent = post.body || "";
const plainText = rawContent
.replace(/^#.*$/gm, "")
.replace(/^###.*$/gm, "")
.replace(/\*\*([^*]+)\*\*/g, "$1")
.replace(/\*([^*]+)\*/g, "$1")
.replace(/_([^_]+)_/g, "$1")
.replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
.replace(/^>.*$/gm, "")
.replace(/`[^`]+`/g, "")
.replace(/^[-*]\s+/gm, "")
.trim();
const words = plainText
.split(/\s+/)
.filter((w) => w.length > 0)
.slice(0, 35);
const excerpt = words.join(" ") + (words.length === 35 ? "..." : "");
const canonicalUrl = new URL(`/${locale}/${news_slug}/${post.id}`, Astro.site);
const imageUrl = post.data.thumbnail
? new URL(post.data.thumbnail, Astro.site).toString()
: null;
const localeDate = new Intl.DateTimeFormat(locale || "es", {
year: "numeric",
month: "long",
day: "numeric",
}).format(post.data.date);
const y = post.data.date.getFullYear();
const m = String(post.data.date.getMonth() + 1).padStart(2, "0");
const d = String(post.data.date.getDate()).padStart(2, "0");
const dateISO = `${y}-${m}-${d}`;
---
<MainLayout
title={post.data.title}
description={excerpt}
image={post.data.thumbnail}
url={canonicalUrl.toString()}
date={post.data.date}
>
<script
type="application/ld+json"
set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "NewsArticle",
headline: post.data.title,
datePublished: post.data.date,
description: excerpt,
image: imageUrl,
url: canonicalUrl.toString(),
author: {
"@type": "Organization",
name: "Centro del Reino de Paz y Justicia",
},
})}
></script>
<div class="container mx-auto md:py-16 py-8">
<Header />
</div>
<a class="block">
<TitleSection title={post.data.title} />
</a>
<div class="container mx-auto">
<a class="block">
{
post.data.gallery?.length ? (
<CarouselSection images={post.data.gallery} />
) : post.data.thumbnail ? (
<Image
src={post.data.thumbnail}
alt={post.data.title}
class="hover:opacity-90 transition-opacity"
/>
) : null
}
</a>
</div>
<div class="grid md:grid-cols-10 container mx-auto">
<div
id="article-content"
class="md:col-span-7 content bg-white p-8 md:p-20 prose-p:mb-4 text-[#003421] text-justify"
>
<article id="article-body">
<time
id="article-date"
class="block text-center text-[#003421]/60 text-sm mb-8 hidden"
datetime={dateISO}
>
{localeDate}
</time>
<Content />
</article>
</div>
<div class="md:col-span-3 bg-tertiary md:sticky top-0 self-start">
{post.data.youtube && <YouTube id={post.data.youtube} />}
{
post.data.gallery &&
post.data.gallery.map((galleryImage, i) => (
<button type="button" data-lightbox-index={i} data-lightbox-src={galleryImage.image} data-lightbox-alt={galleryImage.text} class="cursor-pointer block w-full text-left">
<Image src={galleryImage.image} alt={galleryImage.text} />
</button>
))
}
</div>
</div>
</MainLayout>
<FooterSection />

View File

@ -9,27 +9,30 @@ import TitleSection from "../../../components/section/TitleSection.astro";
import FooterSection from "../../../components/section/FooterSection.astro";
import { getLocalizedRoute } from "@/i18n";
export const prerender = true;
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
const posts = await getCollection("editorial");
return posts.map((post) => ({
params: {
id: post.id,
locale: post.data.locale,
editorial_slug: getLocalizedRoute("editorial", post.data.locale),
},
props: { post },
}));
const paths = [];
for (const collection of ["news", "editorial"]) {
const posts = await getCollection(collection);
for (const post of posts) {
paths.push({
params: {
id: post.id,
locale: post.data.locale,
section: getLocalizedRoute(collection, post.data.locale),
},
props: { post },
});
}
}
return paths;
}
const { locale, editorial_slug } = Astro.params;
// 2. For your template, you can get the entry directly from the prop
const { locale, section } = Astro.params;
const { post } = Astro.props;
const routeKey = post.collection;
const { Content } = await render(post);
const baseSlug = editorial_slug;
const rawContent = post.body || "";
const plainText = rawContent
.replace(/^#.*$/gm, "")
@ -48,7 +51,7 @@ const words = plainText
.slice(0, 35);
const excerpt = words.join(" ") + (words.length === 35 ? "..." : "");
const canonicalUrl = new URL(`/${locale}/${editorial_slug}/${post.id}`, Astro.site);
const canonicalUrl = new URL(`/${locale}/${section}/${post.id}`, Astro.site);
const imageUrl = post.data.thumbnail
? new URL(post.data.thumbnail, Astro.site).toString()
: null;
@ -61,6 +64,8 @@ const y = post.data.date.getFullYear();
const m = String(post.data.date.getMonth() + 1).padStart(2, "0");
const d = String(post.data.date.getDate()).padStart(2, "0");
const dateISO = `${y}-${m}-${d}`;
const schemaType = routeKey === "news" ? "NewsArticle" : "Article";
---
<MainLayout
@ -74,7 +79,7 @@ const dateISO = `${y}-${m}-${d}`;
type="application/ld+json"
set:html={JSON.stringify({
"@context": "https://schema.org",
"@type": "Article",
"@type": schemaType,
headline: post.data.title,
datePublished: post.data.date,
description: excerpt,

View File

@ -2,47 +2,37 @@
import MainLayout from "@/layouts/MainLayout.astro"
import Header from "@/components/Header.astro"
import NewsList from "@/components/cards/NewsList.astro";
import { getCollection, getEntry } from "astro:content";
import { getCollection } from "astro:content";
import FooterSection from "@/components/section/FooterSection.astro";
import { createTranslator, getLocalizedRoute, routeTranslations } from '@/i18n';
import { createTranslator, getRouteKeyFromSlug } from '@/i18n';
const tl = createTranslator(Astro.currentLocale);
export function getStaticPaths() {
const locales = Object.keys(routeTranslations.news);
return locales.map((locale) => ({
params: {
locale,
news_slug: getLocalizedRoute('news', locale)
},
}));
}
const { locale, section } = Astro.params;
const routeKey = getRouteKeyFromSlug(section);
const { locale, news_slug } = Astro.params;
const newsItems = await getCollection("news", (post)=>{
const items = await getCollection(routeKey, (post)=>{
const currentLocale = Astro.currentLocale;
return post.data.locale == currentLocale
});
const sortedPosts = [...newsItems]
const sortedPosts = [...items]
.sort((a, b) => {
const dateDiff = new Date(b.data.date).getTime() - new Date(a.data.date).getTime()
if (dateDiff !== 0) return dateDiff
return (a.data.order ?? 0) - (b.data.order ?? 0)
});
const allTags = [...new Set(
const allTags = routeKey === "news" ? [...new Set(
sortedPosts
.filter(p => p.data.tags && p.data.tags.length > 0)
.flatMap(p => p.data.tags)
.filter((tag): tag is string => tag !== undefined)
)].sort();
)].sort() : [];
const allYears = [...new Set(
const allYears = routeKey === "news" ? [...new Set(
sortedPosts.map(p => new Date(p.data.date).getFullYear())
)].sort((a, b) => b - a);
)].sort((a, b) => b - a) : [];
---
<MainLayout >
@ -51,49 +41,49 @@ const allYears = [...new Set(
</div>
<div class="container mx-auto mt-4">
<div class="flex flex-col lg:w-1/2 items-center mx-auto py-8">
<h1 class="text-white text-2xl uppercase font-bold text-center mb-4 font-primary md:mt-20 mt-10">{tl("news.title")}</h1>
<h2 class="text-white text-3xl lg:text-5xl font-bold text-center font-secondary mb-4 md:p-0 px-2">{tl("news.text")}</h2>
</div>
<h1 class="text-white text-2xl uppercase font-bold text-center mb-4 font-primary md:mt-20 mt-10">{tl(routeKey + ".title")}</h1>
<h2 class="text-white text-3xl lg:text-5xl font-bold text-center font-secondary mb-4 md:p-0 px-2">{tl(routeKey + ".text")}</h2>
</div>
{allTags.length > 0 && (
<div class="container mx-auto mb-8 px-4 md:px-0">
<div class="flex flex-nowrap md:flex-wrap gap-2 md:justify-center overflow-x-auto md:overflow-visible pb-2 md:pb-0">
<button class="filter-btn px-4 py-2 font-primary text-sm cursor-pointer transition-colors whitespace-nowrap" data-tag="all">
{tl("news.all")}
</button>
{allTags.map((tag) => (
<button class="filter-btn px-4 py-2 font-primary text-sm cursor-pointer transition-colors whitespace-nowrap" data-tag={tag!}>
{tag}
{routeKey === "news" && allTags.length > 0 && (
<div class="container mx-auto mb-8 px-4 md:px-0">
<div class="flex flex-nowrap md:flex-wrap gap-2 md:justify-center overflow-x-auto md:overflow-visible pb-2 md:pb-0">
<button class="filter-btn px-4 py-2 font-primary text-sm cursor-pointer transition-colors whitespace-nowrap" data-tag="all">
{tl("news.all")}
</button>
))}
</div>
</div>
)}
<div class="flex flex-col md:gap-0 gap-2 lg:max-w-4xl mx-auto bg-white p-4 md:p-8">
{allYears.length > 0 && (
<div class="container mb-4">
<div class="flex justify-start md:justify-end">
<select id="year-filter" class="bg-[#003421] text-[#EBE5D0] px-4 py-2 font-primary text-sm cursor-pointer border-none outline-none">
<option value="all">{tl("news.allYears")}</option>
{allYears.map((year) => (
<option value={year}>{year}</option>
{allTags.map((tag) => (
<button class="filter-btn px-4 py-2 font-primary text-sm cursor-pointer transition-colors whitespace-nowrap" data-tag={tag!}>
{tag}
</button>
))}
</select>
</div>
</div>
)}
<div class="flex flex-col md:gap-0 gap-2 lg:max-w-4xl mx-auto bg-white p-4 md:p-8">
{routeKey === "news" && allYears.length > 0 && (
<div class="container mb-4">
<div class="flex justify-start md:justify-end">
<select id="year-filter" class="bg-[#003421] text-[#EBE5D0] px-4 py-2 font-primary text-sm cursor-pointer border-none outline-none">
<option value="all">{tl("news.allYears")}</option>
{allYears.map((year) => (
<option value={year}>{year}</option>
))}
</select>
</div>
</div>
)}
{
sortedPosts.map((item) => (
<div class="news-item" data-tags={routeKey === "news" ? JSON.stringify(item.data.tags || []) : "[]"} data-year={routeKey === "news" ? new Date(item.data.date).getFullYear() : ""}>
<NewsList data={item} content={{ body: item.body }} routeKey={routeKey} />
</div>
))
}
</div>
)}
{
sortedPosts.map((item) => (
<div class="news-item" data-tags={JSON.stringify(item.data.tags || [])} data-year={new Date(item.data.date).getFullYear()}>
<NewsList data={item} content={{ body: item.body }} />
</div>
))
}
</div>
</div>
<FooterSection />
</div>
<FooterSection />
</MainLayout>