feat: implement dynamic news pages and listing components with internationalization support
This commit is contained in:
parent
5731cf245d
commit
aa54417618
|
|
@ -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(', ')}<br />
|
||||
({nicedate}):
|
||||
</p>
|
||||
<h3 class="text-2xl font-bold mb-8"><a href={`/${locale}/news/${data.id}`}>{data.data.title}</a></h3>
|
||||
<h3 class="text-2xl font-bold mb-8"><a href={`/${locale}/${getLocalizedRoute('news', locale)}/${data.id}`}>{data.data.title}</a></h3>
|
||||
|
||||
<div>
|
||||
<Image
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import utc from "dayjs/plugin/utc";
|
|||
const regionNames = new Intl.DisplayNames(["es"], { type: "region" });
|
||||
|
||||
const locale = Astro.currentLocale;
|
||||
import { createTranslator } from "@/i18n";
|
||||
import { createTranslator, getLocalizedRoute } from "@/i18n";
|
||||
const tl = createTranslator(Astro.currentLocale);
|
||||
dayjs.extend(utc);
|
||||
dayjs.locale(locale);
|
||||
|
|
@ -21,7 +21,7 @@ const countryName = data?.data?.country
|
|||
const locationArray = [data.data.city, data.data.state, countryName];
|
||||
const location = locationArray.filter(Boolean).join(", ");
|
||||
|
||||
const newsUrl = `/${locale}/news/${data.id}`;
|
||||
const newsUrl = `/${locale}/${getLocalizedRoute('news', locale)}/${data.id}`;
|
||||
|
||||
const rawContent = content?.body || "";
|
||||
const plainText = rawContent
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ const newsItems = await getCollection("news", (post)=>{
|
|||
const currentLocale = Astro.currentLocale;
|
||||
return post.data.locale == currentLocale
|
||||
});
|
||||
|
||||
import { createTranslator, t } from '../../i18n';
|
||||
import { createTranslator, getLocalizedRoute } from '../../i18n';
|
||||
const tl = createTranslator(Astro.currentLocale);
|
||||
---
|
||||
<div id="news" class="bg-[#22523F] py-12 lg:py-20">
|
||||
|
|
@ -19,7 +18,7 @@ const tl = createTranslator(Astro.currentLocale);
|
|||
<h4 class="text-white text-2xl uppercase font-bold text-center mb-4 font-primary">{tl("news.title")}</h4>
|
||||
<h2 class="text-white text-3xl lg:text-5xl font-bold text-center font-secondary mb-4">{tl("news.text")}</h2>
|
||||
<p class="text-white text-xl text-center">{tl("news.text2")}</p>
|
||||
<Button class="px-6 py-3 uppercase mt-4" url=`/${currentLocale}/news` variant="primary" title={tl("news.buttonLable")} />
|
||||
<Button class="px-6 py-3 uppercase mt-4" url={`/${currentLocale}/${getLocalizedRoute('news', currentLocale)}`} variant="primary" title={tl("news.buttonLable")} />
|
||||
</div>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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}/`));
|
||||
---
|
||||
|
||||
<html lang={currentLocale} dir={direction} class="scroll-smooth">
|
||||
|
|
@ -35,7 +39,7 @@ const direction = currentLocale === 'he' ? 'rtl' : 'ltr';
|
|||
});
|
||||
</script>
|
||||
<body class="font-primary">
|
||||
{Astro.url.pathname.includes('/news/') && (
|
||||
{isNewsPage && (
|
||||
<ShareSticky client:only url={Astro.url.href} />
|
||||
)}
|
||||
<slot />
|
||||
|
|
|
|||
|
|
@ -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 ? "..." : "");
|
||||
---
|
||||
|
||||
<MainLayout
|
||||
title={post.data.title}
|
||||
description={excerpt}
|
||||
image={post.data.thumbnail}
|
||||
url={new URL(`/${locale}/${news_slug}/${post.id}`, Astro.site)}
|
||||
>
|
||||
<div class="container mx-auto md:py-16 py-8">
|
||||
<Header />
|
||||
</div>
|
||||
|
||||
<a href={`/${locale}/${news_slug}/${post.id}`} class="block">
|
||||
<TitleSection title={post.data.title} />
|
||||
</a>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<a href={`/${locale}/${news_slug}/${post.id}`} 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">
|
||||
<div
|
||||
class="md:col-span-7 content bg-white p-8 md:p-20 prose-p:mb-4 text-[#003421] text-justify"
|
||||
>
|
||||
<!-- <p class="text-lg font-semibold text-tertiary mb-8 pb-6 border-b border-tertiary/20 italic">
|
||||
{excerpt}
|
||||
</p> -->
|
||||
<Content />
|
||||
</div>
|
||||
<div class="md:col-span-3 bg-tertiary md:sticky top-0 h-fit">
|
||||
{post.data.youtube && <YouTube id={post.data.youtube} />}
|
||||
|
||||
{
|
||||
post.data.gallery &&
|
||||
post.data.gallery.map((galleryImage) => (
|
||||
<Image src={galleryImage.image} alt={galleryImage.text} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</MainLayout>
|
||||
|
||||
<FooterSection />
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ? '...' : '');
|
||||
|
||||
---
|
||||
|
||||
<MainLayout
|
||||
title={post.data.title}
|
||||
description={excerpt}
|
||||
image={post.data.thumbnail}
|
||||
url={new URL(`/${locale}/${"news"}/${post.id}`, Astro.site)}
|
||||
>
|
||||
<div class="container mx-auto md:py-16 py-8">
|
||||
<Header />
|
||||
</div>
|
||||
|
||||
<a href={`/${locale}/news/${post.id}`} class="block">
|
||||
<TitleSection title={post.data.title} />
|
||||
</a>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<a href={`/${locale}/news/${post.id}`} 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 class="grid md:grid-cols-10">
|
||||
<div class="md:col-span-7 content bg-white p-8 md:p-20 prose-p:mb-4 text-[#003421] text-justify">
|
||||
<!-- <p class="text-lg font-semibold text-tertiary mb-8 pb-6 border-b border-tertiary/20 italic">
|
||||
{excerpt}
|
||||
</p> -->
|
||||
<Content />
|
||||
</div>
|
||||
<div class="md:col-span-3 bg-tertiary md:sticky top-0 h-fit">
|
||||
{ post.data.youtube && (
|
||||
<YouTube id={post.data.youtube} />
|
||||
)}
|
||||
|
||||
{post.data.gallery && (
|
||||
post.data.gallery.map(galleryImage => (
|
||||
<Image src={galleryImage.image} alt={galleryImage.text} />
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</MainLayout>
|
||||
|
||||
<FooterSection />
|
||||
|
|
@ -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();
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue