diff --git a/src/components/BlogList.tsx b/src/components/BlogList.tsx new file mode 100644 index 0000000..61b3fe9 --- /dev/null +++ b/src/components/BlogList.tsx @@ -0,0 +1,124 @@ +import React from 'react'; +import { useTranslations, type Lang } from '../i18n/utils'; + +/** + * Blog post interface definition + */ +interface BlogPost { + title: string; + description: string; + image: string; + slug: string; + tags: string[]; + date: string; + readTime: string; +} + +/** + * Props interface for BlogList component + */ +interface BlogListProps { + posts: BlogPost[]; + lang: Lang; + baseUrl?: string; // Base URL for blog posts, defaults to '/blog/posts/' +} + +/** + * Reusable blog list component that displays blog posts in a grid layout + * @param posts - Array of blog posts to display + * @param lang - Current language for internationalization + * @param baseUrl - Base URL for blog post links + */ +export default function BlogList({ posts, lang, baseUrl = '/blog/posts/' }: BlogListProps) { + const t = useTranslations(lang); + + // Adjust base URL for Chinese language + const postBaseUrl = lang === 'zh' ? '/zh/blog/posts/' : baseUrl; + + // Get localized "Read More" text + const readMoreText = lang === 'zh' ? '阅读更多' : 'Read More'; + + return ( +
+ {posts.map((post, index) => ( +
+
+
+ {/* Featured Image */} +
+ {post.title} +
+
+ + {/* Content */} +
+
+

+ + {post.title} + +

+ +

+ {post.description} +

+ + {/* Tags */} +
+ {post.tags.map((tag, tagIndex) => ( + + {tag} + + ))} +
+
+ + {/* Meta Info */} +
+
+ + + + + {post.date} + + {post.readTime} +
+ + + {readMoreText} + + + + +
+
+
+
+
+ ))} +
+ ); +} + +/** + * CSS styles for line clamping (to be included in global styles) + */ +export const blogListStyles = ` +.line-clamp-3 { + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} +`; \ No newline at end of file diff --git a/src/components/GlassHeader.tsx b/src/components/GlassHeader.tsx index 534b3c2..8e61b18 100644 --- a/src/components/GlassHeader.tsx +++ b/src/components/GlassHeader.tsx @@ -1,6 +1,6 @@ -import ThemeToggle from "./ui/theme-toggle"; import { personalInfo } from "@/lib/data"; import LanguageSwitcher from "./LanguageSwitcher"; +import ThemeToggle from "./ui/theme-toggle"; import { useTranslations, getLocalizedPath, type Lang } from "@/i18n/utils"; import { useState, useEffect } from "react"; import { Menu, X } from "lucide-react"; @@ -45,10 +45,11 @@ export default function GlassHeader({ lang }: GlassHeaderProps) { { key: 'nav.about', icon: '👨‍💻 ', sectionId: 'about' }, { key: 'nav.services', icon: '🛠️ ', sectionId: 'services' }, { key: 'nav.projects', icon: '🚀 ', sectionId: 'projects' }, + { key: 'nav.blog', icon: '📝 ', href: getLocalizedPath('/blog', lang) }, ].map((item) => ( {item.icon} @@ -87,10 +88,11 @@ export default function GlassHeader({ lang }: GlassHeaderProps) { { key: 'nav.about', icon: '👨‍💻 ', sectionId: 'about' }, { key: 'nav.services', icon: '🛠️ ', sectionId: 'services' }, { key: 'nav.projects', icon: '🚀 ', sectionId: 'projects' }, + { key: 'nav.blog', icon: '📝 ', href: getLocalizedPath('/blog', lang) }, ].map((item) => ( diff --git a/src/i18n/ui.ts b/src/i18n/ui.ts index 59c6fab..025b6ba 100644 --- a/src/i18n/ui.ts +++ b/src/i18n/ui.ts @@ -12,6 +12,7 @@ export const ui = { 'nav.about': 'About', 'nav.services': 'Services', 'nav.projects': 'Projects', + 'nav.blog': 'Blog', 'nav.contact': 'Contact', 'site.title': 'Joy Zhao - Full Stack Developer', 'site.description': 'Full Stack Developer specializing in React, Node.js, and modern web technologies', @@ -31,6 +32,7 @@ export const ui = { 'nav.about': '关于', 'nav.services': '服务', 'nav.projects': '项目', + 'nav.blog': '博客', 'nav.contact': '联系', 'site.title': 'Joy Zhao - 全栈开发者', 'site.description': '专注于 React、Node.js 和现代 Web 技术的全栈开发者', diff --git a/src/layouts/BlogLayout.astro b/src/layouts/BlogLayout.astro new file mode 100644 index 0000000..dda3ffd --- /dev/null +++ b/src/layouts/BlogLayout.astro @@ -0,0 +1,94 @@ +--- +import { type Lang } from '@/i18n/utils'; +import { defaultLang } from '@/i18n/ui'; +import GlassHeader from '@/components/GlassHeader'; +import Footer from '@/components/Footer'; +import "../styles/global.css"; + +export interface Props { + title: string; + description?: string; +} + +const { title, description = 'Explore my latest thoughts on coding, tech trends, and developer life.' } = Astro.props; +const lang = Astro.currentLocale as Lang || defaultLang; +--- + + + + + + + + + + {title} + + + + + +
+
+ + + + +
+ +
+ + +