From 3869bb23bbfdf25efe342aedb7c8674f5867500b Mon Sep 17 00:00:00 2001 From: Caihaohan Date: Mon, 29 Dec 2025 19:58:23 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E4=BC=98=E5=8C=96=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=E5=9B=BD=E9=99=85=E5=8C=96=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将客户端组件改为服务端组件,使用 getTranslations - LocaleSwitcher 简化为接收 switchUrl prop - ProjectCard/ProjectList/ExternalLinkCard 等组件支持 i18n - 优化 webhook 去重查询,关联 links 数据 - 移除 ProjectDetail 中的客户端交互逻辑(ShareButtons 暂时禁用) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- src/app/[locale]/page.tsx | 7 ++- src/app/[locale]/projects/page.tsx | 7 ++- src/app/api/webhook/projects/route.ts | 15 +++++- src/components/locale/LocaleSwitcher.tsx | 23 +------- src/components/project/ExternalLinkCard.tsx | 35 ++++++++---- src/components/project/ProjectCard.tsx | 22 +++++--- src/components/project/ProjectDetail.tsx | 60 ++++++--------------- src/components/project/ProjectList.tsx | 7 ++- src/components/project/ShareButtons.tsx | 52 ++++++++++++++++++ src/components/search/SearchBar.tsx | 8 +-- 10 files changed, 143 insertions(+), 93 deletions(-) create mode 100644 src/components/project/ShareButtons.tsx diff --git a/src/app/[locale]/page.tsx b/src/app/[locale]/page.tsx index 279bf83..ae51ac8 100644 --- a/src/app/[locale]/page.tsx +++ b/src/app/[locale]/page.tsx @@ -12,6 +12,7 @@ interface HomePageProps { export default async function HomePage({ params }: HomePageProps) { const { locale } = await params const t = await getTranslations('home') + const tCommon = await getTranslations('common') const [projectsData, tags] = await Promise.all([ getProjects({ limit: 6 }), @@ -33,7 +34,11 @@ export default async function HomePage({ params }: HomePageProps) { Discover and explore high-quality AI projects from across the web. Curated for developers and enthusiasts.

}> - + diff --git a/src/app/[locale]/projects/page.tsx b/src/app/[locale]/projects/page.tsx index 9b6fcdd..b36757a 100644 --- a/src/app/[locale]/projects/page.tsx +++ b/src/app/[locale]/projects/page.tsx @@ -15,6 +15,7 @@ export default async function ProjectsPage({ searchParams, }: ProjectsPageProps) { const t = await getTranslations('home') + const tCommon = await getTranslations('common') const [resolvedParams, resolvedSearchParams] = await Promise.all([params, searchParams]) const { locale } = resolvedParams @@ -47,7 +48,11 @@ export default async function ProjectsPage({ {/* Search and Filter Section */}
}> - +
diff --git a/src/app/api/webhook/projects/route.ts b/src/app/api/webhook/projects/route.ts index ddb1844..6141298 100644 --- a/src/app/api/webhook/projects/route.ts +++ b/src/app/api/webhook/projects/route.ts @@ -28,7 +28,11 @@ async function findExistingProject(projectData: ProjectInput) { url: githubLink.url, }, include: { - project: true, + project: { + include: { + links: true, + }, + }, }, }) @@ -51,7 +55,11 @@ async function findExistingProject(projectData: ProjectInput) { url: websiteLink.url, }, include: { - project: true, + project: { + include: { + links: true, + }, + }, }, }) @@ -70,6 +78,9 @@ async function findExistingProject(projectData: ProjectInput) { const existingBySlug = await prisma.project.findUnique({ where: { slug }, + include: { + links: true, + }, }) if (existingBySlug) { diff --git a/src/components/locale/LocaleSwitcher.tsx b/src/components/locale/LocaleSwitcher.tsx index dfe5e82..bee6c26 100644 --- a/src/components/locale/LocaleSwitcher.tsx +++ b/src/components/locale/LocaleSwitcher.tsx @@ -1,6 +1,5 @@ 'use client' -import { usePathname } from 'next/navigation' import Link from 'next/link' const locales = ['zh', 'en'] as const @@ -9,30 +8,12 @@ const localeNames: Record = { en: 'EN' } -export function LocaleSwitcher({ currentLocale }: { currentLocale: string }) { - const pathname = usePathname() - - // 移除当前语言前缀,获取基础路径 - const getBasePathname = () => { - const segments = pathname.split('/').filter(Boolean) - if (segments[0] && locales.includes(segments[0] as any)) { - return '/' + segments.slice(1).join('/') - } - return pathname - } - - const basePathname = getBasePathname() - - const switchLocale = (newLocale: string) => { - // 切换到另一种语言 - return `/${newLocale}${basePathname}` - } - +export function LocaleSwitcher({ currentLocale, switchUrl }: { currentLocale: string; switchUrl: string }) { const otherLocale = currentLocale === 'zh' ? 'en' : 'zh' return ( {localeNames[currentLocale]} / {localeNames[otherLocale]} diff --git a/src/components/project/ExternalLinkCard.tsx b/src/components/project/ExternalLinkCard.tsx index 398a9b4..c998b2b 100644 --- a/src/components/project/ExternalLinkCard.tsx +++ b/src/components/project/ExternalLinkCard.tsx @@ -1,26 +1,39 @@ -import { ExternalLink } from '@prisma/client' +import { ExternalLink, LinkType } from '@prisma/client' +import { getTranslations } from 'next-intl/server' interface ExternalLinkCardProps { links: ExternalLink[] + locale: string } -const linkTypeNames = { - WEBSITE: '官网', - GITHUB: 'GitHub', - HUGGINGFACE: 'HuggingFace', - PAPER: '论文', -} +export async function ExternalLinkCard({ links, locale }: ExternalLinkCardProps) { + const t = await getTranslations('project') -export function ExternalLinkCard({ links }: ExternalLinkCardProps) { if (links.length === 0) { return null } + // Build type map for all link types + const typeMap: Record = { + WEBSITE: t('website'), + GITHUB: t('github'), + HUGGINGFACE: t('huggingface'), + PAPER: t('paper'), + } + + // Pre-resolve all link names + const linkItems = await Promise.all( + links.map(async (link) => ({ + ...link, + displayName: link.title || typeMap[link.type] + })) + ) + return (
-

外部链接

+

{t('externalLinks')}

- {links.map((link) => ( + {linkItems.map((link) => (
- {link.title || linkTypeNames[link.type]} + {link.displayName}
{link.url}
diff --git a/src/components/project/ProjectCard.tsx b/src/components/project/ProjectCard.tsx index 1023cd7..d4d40b9 100644 --- a/src/components/project/ProjectCard.tsx +++ b/src/components/project/ProjectCard.tsx @@ -1,4 +1,5 @@ import Link from 'next/link' +import { getTranslations } from 'next-intl/server' interface ProjectCardProps { project: { @@ -30,11 +31,17 @@ function getProjectIcon(tags: Array<{ name: string }>): string { return '✨' } -export function ProjectCard({ project, locale, featured = false }: ProjectCardProps) { +export async function ProjectCard({ project, locale, featured = false }: ProjectCardProps) { + const t = await getTranslations('common') const icon = getProjectIcon(project.tags) const displayName = locale === 'en' && project.nameEn ? project.nameEn : project.name const displayDescription = locale === 'en' && project.descriptionEn ? project.descriptionEn : project.description + // Helper to get display name for tag based on locale + const getTagName = (tag: { name: string; nameEn?: string | null }) => { + return locale === 'en' && tag.nameEn ? tag.nameEn : tag.name + } + return (
- {tag.name} + {getTagName(tag)} ))}
@@ -71,7 +78,7 @@ export function ProjectCard({ project, locale, featured = false }: ProjectCardPr href={`/${locale}/projects/${project.slug}`} className="inline-flex items-center font-display font-bold text-sm uppercase hover:text-secondary dark:hover:text-primary group" > - VIEW DETAILS{' '} + {t('viewDetails')}{' '}
@@ -80,21 +87,22 @@ export function ProjectCard({ project, locale, featured = false }: ProjectCardPr } // Special card for "Submit Your Project" CTA -export function SubmitProjectCard({ locale }: { locale: string }) { +export async function SubmitProjectCard({ locale }: { locale: string }) { + const t = await getTranslations('project') return (
+
-

Submit Your Project

+

{t('submitProjectTitle')}

- Have an AI tool worth sharing? Add it to Agent Park. + {t('submitProjectDescription')}

- SUBMIT NOW + {t('submitNow')}
) diff --git a/src/components/project/ProjectDetail.tsx b/src/components/project/ProjectDetail.tsx index 2c3f9c5..1e834f7 100644 --- a/src/components/project/ProjectDetail.tsx +++ b/src/components/project/ProjectDetail.tsx @@ -1,7 +1,6 @@ -'use client' - import Link from 'next/link' import { MarkdownContent } from './MarkdownContent' +import { ShareButtons } from './ShareButtons' interface ProjectDetailProps { project: { @@ -31,14 +30,22 @@ interface ProjectDetailProps { locale: string } -// Helper function to format date +// Helper function to format date - using ISO format for consistency function formatDate(date: Date | string, locale: string): string { const dateObj = typeof date === 'string' ? new Date(date) : date - const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' } - return dateObj.toLocaleDateString(locale === 'en' ? 'en-US' : 'zh-CN', options) + const year = dateObj.getFullYear() + const month = dateObj.getMonth() + 1 + const day = dateObj.getDate() + + if (locale === 'en') { + const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] + return `${monthNames[month - 1]} ${day}, ${year}` + } else { + return `${year}年${month}月${day}日` + } } -export function ProjectDetail({ project, locale }: ProjectDetailProps) { +export async function ProjectDetail({ project, locale }: ProjectDetailProps) { const displayName = locale === 'en' && project.nameEn ? project.nameEn : project.name const displayDescription = locale === 'en' && project.descriptionEn ? project.descriptionEn : project.description @@ -49,20 +56,6 @@ export function ProjectDetail({ project, locale }: ProjectDetailProps) { const categoryEn = project.tags[0]?.nameEn || project.tags[0]?.name || 'AI Agent' const displayCategory = locale === 'en' ? categoryEn : category - const handleShare = () => { - if (typeof window !== 'undefined') { - const url = encodeURIComponent(window.location.href) - const text = encodeURIComponent(`Check out ${displayName} on Agent Park`) - window.open(`https://twitter.com/intent/tweet?url=${url}&text=${text}`, '_blank') - } - } - - const handleCopyLink = () => { - if (typeof window !== 'undefined') { - navigator.clipboard.writeText(window.location.href) - } - } - return ( <> {/* Header Section */} @@ -153,31 +146,8 @@ npm install`} )} - {/* Share and Feedback Section */} -
-
- - -
-
- DID THIS AGENT HELP YOU? - -
-
+ {/* Share and Feedback Section - Temporarily disabled for debugging */} + {/* */} ) } diff --git a/src/components/project/ProjectList.tsx b/src/components/project/ProjectList.tsx index 44f4f71..50ca0e6 100644 --- a/src/components/project/ProjectList.tsx +++ b/src/components/project/ProjectList.tsx @@ -1,4 +1,5 @@ import { ProjectCard, SubmitProjectCard } from './ProjectCard' +import { getTranslations } from 'next-intl/server' interface ProjectListProps { projects: Array<{ @@ -19,11 +20,13 @@ interface ProjectListProps { featured?: boolean } -export function ProjectList({ projects, locale, featured = false }: ProjectListProps) { +export async function ProjectList({ projects, locale, featured = false }: ProjectListProps) { + const t = await getTranslations('common') + if (projects.length === 0) { return (
-

暂无项目

+

{t('noProjects')}

) } diff --git a/src/components/project/ShareButtons.tsx b/src/components/project/ShareButtons.tsx new file mode 100644 index 0000000..ce96d3c --- /dev/null +++ b/src/components/project/ShareButtons.tsx @@ -0,0 +1,52 @@ +'use client' + +interface ShareButtonsProps { + displayName: string +} + +export function ShareButtons({ displayName }: ShareButtonsProps) { + const handleShare = () => { + const url = encodeURIComponent(window.location.href) + const text = encodeURIComponent(`Check out ${displayName} on Agent Park`) + window.open(`https://twitter.com/intent/tweet?url=${url}&text=${text}`, '_blank') + } + + const handleCopyLink = () => { + navigator.clipboard.writeText(window.location.href) + } + + return ( +
+
+ + +
+
+ DID THIS AGENT HELP YOU? + +
+
+ ) +} diff --git a/src/components/search/SearchBar.tsx b/src/components/search/SearchBar.tsx index 933a47f..8c11a05 100644 --- a/src/components/search/SearchBar.tsx +++ b/src/components/search/SearchBar.tsx @@ -5,9 +5,11 @@ import { useRouter, useSearchParams } from 'next/navigation' interface SearchBarProps { locale: string + searchPlaceholder: string + searchLabel: string } -export function SearchBar({ locale }: SearchBarProps) { +export function SearchBar({ locale, searchPlaceholder, searchLabel }: SearchBarProps) { const router = useRouter() const searchParams = useSearchParams() const [query, setQuery] = useState(searchParams.get('search') || '') @@ -36,7 +38,7 @@ export function SearchBar({ locale }: SearchBarProps) { type="text" value={query} onChange={(e) => setQuery(e.target.value)} - placeholder="Search AI projects..." + placeholder={searchPlaceholder} className="block w-full pl-12 pr-32 py-4 bg-surface-light dark:bg-surface-dark border-2 border-black dark:border-gray-600 text-text-light dark:text-text-dark placeholder-gray-500 focus:ring-0 focus:border-black dark:focus:border-primary font-display shadow-neo transition-all" /> @@ -45,7 +47,7 @@ export function SearchBar({ locale }: SearchBarProps) { type="submit" className="absolute inset-y-2 right-2 px-4 bg-primary text-black font-bold font-display text-sm border-2 border-black hover:bg-yellow-400 transition-colors shadow-neo-sm active:shadow-none active:translate-x-[2px] active:translate-y-[2px]" > - SEARCH + {searchLabel}