From aa54417618a53007bc18e9a773901b71c156e0fb Mon Sep 17 00:00:00 2001 From: "EPAZZ\\estpp" Date: Thu, 7 May 2026 20:50:53 -0500 Subject: [PATCH] feat: implement dynamic news pages and listing components with internationalization support --- src/components/cards/NewsCard.astro | 3 +- src/components/cards/NewsList.astro | 4 +- src/components/section/NewsSection.astro | 5 +- src/i18n/index.ts | 20 +++- src/layouts/MainLayout.astro | 6 +- src/pages/[locale]/[news_slug]/[id].astro | 103 ++++++++++++++++++ .../{news => [news_slug]}/index.astro | 14 ++- src/pages/[locale]/news/[id].astro | 95 ---------------- src/pages/{news => [news_slug]}/[id].astro | 9 +- src/pages/{news => [news_slug]}/index.astro | 11 +- 10 files changed, 163 insertions(+), 107 deletions(-) create mode 100644 src/pages/[locale]/[news_slug]/[id].astro rename src/pages/[locale]/{news => [news_slug]}/index.astro (94%) delete mode 100644 src/pages/[locale]/news/[id].astro rename src/pages/{news => [news_slug]}/[id].astro (90%) rename src/pages/{news => [news_slug]}/index.astro (84%) diff --git a/src/components/cards/NewsCard.astro b/src/components/cards/NewsCard.astro index 9c6b2d1..db3858e 100644 --- a/src/components/cards/NewsCard.astro +++ b/src/components/cards/NewsCard.astro @@ -5,6 +5,7 @@ import "dayjs/locale/es"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; const regionNames = new Intl.DisplayNames(['es'], { type: 'region' }); +import { getLocalizedRoute } from "../../i18n"; const locale = Astro.currentLocale; dayjs.extend(utc); @@ -24,7 +25,7 @@ locationArray.filter(Boolean).join(', '); {locationArray.filter(Boolean).join(', ')}
({nicedate}):

-

{data.data.title}

+

{data.data.title}

{ const currentLocale = Astro.currentLocale; return post.data.locale == currentLocale }); - -import { createTranslator, t } from '../../i18n'; +import { createTranslator, getLocalizedRoute } from '../../i18n'; const tl = createTranslator(Astro.currentLocale); ---
@@ -19,7 +18,7 @@ const tl = createTranslator(Astro.currentLocale);

{tl("news.title")}

{tl("news.text")}

{tl("news.text2")}

-
diff --git a/src/i18n/index.ts b/src/i18n/index.ts index c9ecd47..abdc53a 100644 --- a/src/i18n/index.ts +++ b/src/i18n/index.ts @@ -11,9 +11,27 @@ import kr from "./kr.json"; const dictionaries = { es, en, fr, he, uk, pt, ru, rw, kr } as const; export type Locale = keyof typeof dictionaries; -// Optional: type-safe keys from the default dictionary export type I18nKey = keyof typeof es; +export const routeTranslations = { + news: { + es: "noticias", + en: "news", + fr: "informations", + he: "חדשות", + uk: "noticias", + pt: "noticias", + ru: "новости", + rw: "amakuru", + kr: "nouvel", + } +} as const; + +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]]; +} + export function t( locale: string | undefined, key: I18nKey, diff --git a/src/layouts/MainLayout.astro b/src/layouts/MainLayout.astro index 731e103..c3cd7ec 100644 --- a/src/layouts/MainLayout.astro +++ b/src/layouts/MainLayout.astro @@ -8,6 +8,7 @@ import "@fontsource/poppins/500.css"; import "@fontsource/poppins/700.css"; import "@fontsource-variable/kameron"; import ShareSticky from "../components/ShareSticky.vue"; +import { routeTranslations } from "../i18n"; const { title, description, @@ -17,6 +18,9 @@ const { const currentLocale = Astro.currentLocale ?? 'es'; const direction = currentLocale === 'he' ? 'rtl' : 'ltr'; + +const newsSegments = Object.values(routeTranslations.news); +const isNewsPage = newsSegments.some(segment => Astro.url.pathname.includes(`/${segment}/`)); --- @@ -35,7 +39,7 @@ const direction = currentLocale === 'he' ? 'rtl' : 'ltr'; }); - {Astro.url.pathname.includes('/news/') && ( + {isNewsPage && ( )} diff --git a/src/pages/[locale]/[news_slug]/[id].astro b/src/pages/[locale]/[news_slug]/[id].astro new file mode 100644 index 0000000..c3210c2 --- /dev/null +++ b/src/pages/[locale]/[news_slug]/[id].astro @@ -0,0 +1,103 @@ +--- +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 ? "..." : ""); +--- + + +
+
+
+ + + + + + +
+
+ + +
+
+ {post.data.youtube && } + + { + post.data.gallery && + post.data.gallery.map((galleryImage) => ( + {galleryImage.text} + )) + } +
+
+
+ + diff --git a/src/pages/[locale]/news/index.astro b/src/pages/[locale]/[news_slug]/index.astro similarity index 94% rename from src/pages/[locale]/news/index.astro rename to src/pages/[locale]/[news_slug]/index.astro index 42f5cb3..ec46e3b 100644 --- a/src/pages/[locale]/news/index.astro +++ b/src/pages/[locale]/[news_slug]/index.astro @@ -6,9 +6,21 @@ import { getCollection, getEntry } from "astro:content"; import FooterSection from "@/components/section/FooterSection.astro"; -import { createTranslator } from '@/i18n'; +import { createTranslator, getLocalizedRoute, routeTranslations } 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, news_slug } = Astro.params; + const newsItems = await getCollection("news", (post)=>{ const currentLocale = Astro.currentLocale; return post.data.locale == currentLocale diff --git a/src/pages/[locale]/news/[id].astro b/src/pages/[locale]/news/[id].astro deleted file mode 100644 index 04a8aeb..0000000 --- a/src/pages/[locale]/news/[id].astro +++ /dev/null @@ -1,95 +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'; -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 }, - props: { post }, - })); -} - -const { locale } = 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 routeTranslations = { - news: { - es: "noticias", - en: "news", - fr: "actualites", - pt: "noticias", - de: "nachrichten", - } -}; - -const baseSlug = routeTranslations.news[locale] || routeTranslations.news.en; - -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 ? '...' : ''); - ---- - - -
-
-
- - - - - -
- - {post.data.gallery?.length ? ( ) : post.data.thumbnail ? ({post.data.title} ) : null} - - -
-
- - -
-
- { post.data.youtube && ( - - )} - - {post.data.gallery && ( - post.data.gallery.map(galleryImage => ( - {galleryImage.text} - )) - )} -
-
- - - diff --git a/src/pages/news/[id].astro b/src/pages/[news_slug]/[id].astro similarity index 90% rename from src/pages/news/[id].astro rename to src/pages/[news_slug]/[id].astro index 5bede2c..ba3ae7b 100644 --- a/src/pages/news/[id].astro +++ b/src/pages/[news_slug]/[id].astro @@ -7,22 +7,27 @@ 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 }, + params: { + id: post.id, + news_slug: getLocalizedRoute('news', post.data.locale) + }, props: { post }, })); } +const { news_slug } = Astro.params; const { post } = Astro.props; const { Content } = await render(post); console.log("astro site", Astro.site); const baseUrl = Astro.site ?? "https://mk8nrc8p-4321.brs.devtunnels.ms"; -const pageUrl = new URL(`/es/news/${post.id}`, baseUrl).toString(); +const pageUrl = new URL(`/${post.data.locale}/${news_slug}/${post.id}`, baseUrl).toString(); --- diff --git a/src/pages/news/index.astro b/src/pages/[news_slug]/index.astro similarity index 84% rename from src/pages/news/index.astro rename to src/pages/[news_slug]/index.astro index f2a1e5b..269d6e2 100644 --- a/src/pages/news/index.astro +++ b/src/pages/[news_slug]/index.astro @@ -8,9 +8,18 @@ import FooterSection from "@/components/section/FooterSection.astro"; import NewsList from "@/components/cards/NewsList.astro"; -import { createTranslator, t } from '@/i18n'; +import { createTranslator, getLocalizedRoute, routeTranslations } from '@/i18n'; const tl = createTranslator(Astro.currentLocale); +export function getStaticPaths() { + const locales = Object.keys(routeTranslations.news); + return locales.map((locale) => ({ + params: { + news_slug: getLocalizedRoute('news', locale) + }, + })); +} + const newsItems = await getCollection("news", (post)=>{ const currentLocale = Astro.currentLocale; return post.data.locale == currentLocale