feat: 添加关键词词云页面国际化支持
This commit is contained in:
@@ -15,12 +15,15 @@ export interface KeywordData {
|
|||||||
word: string;
|
word: string;
|
||||||
trendScore: number;
|
trendScore: number;
|
||||||
description: string;
|
description: string;
|
||||||
|
descriptionEn?: string | null;
|
||||||
detailPoints: string[];
|
detailPoints: string[];
|
||||||
|
detailPointsEn?: string[] | null;
|
||||||
visualConfig: VisualConfig;
|
visualConfig: VisualConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CloudWordProps {
|
interface CloudWordProps {
|
||||||
data: KeywordData;
|
data: KeywordData;
|
||||||
|
locale: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 颜色映射
|
// 颜色映射
|
||||||
@@ -31,11 +34,15 @@ const colorMap: Record<string, string> = {
|
|||||||
gray: 'bg-gray-100 dark:bg-gray-700',
|
gray: 'bg-gray-100 dark:bg-gray-700',
|
||||||
};
|
};
|
||||||
|
|
||||||
export function CloudWord({ data }: CloudWordProps) {
|
export function CloudWord({ data, locale }: CloudWordProps) {
|
||||||
const { word, visualConfig, description, detailPoints } = data;
|
const { word, visualConfig, description, descriptionEn, detailPoints, detailPointsEn } = data;
|
||||||
const { color, size, border, rotation } = visualConfig;
|
const { color, size, border, rotation } = visualConfig;
|
||||||
const colorClass = colorMap[color];
|
const colorClass = colorMap[color];
|
||||||
|
|
||||||
|
// 根据语言选择内容
|
||||||
|
const displayDescription = locale === 'en' && descriptionEn ? descriptionEn : description;
|
||||||
|
const displayPoints = locale === 'en' && detailPointsEn ? detailPointsEn : detailPoints;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
@@ -63,8 +70,8 @@ export function CloudWord({ data }: CloudWordProps) {
|
|||||||
{word}
|
{word}
|
||||||
<WordPopover
|
<WordPopover
|
||||||
title={word}
|
title={word}
|
||||||
description={description}
|
description={displayDescription}
|
||||||
points={detailPoints}
|
points={displayPoints}
|
||||||
titleColor={color}
|
titleColor={color}
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -6,7 +6,18 @@ import { QuarterNavigator } from './QuarterNavigator';
|
|||||||
import { ProgressIndicator } from './ProgressIndicator';
|
import { ProgressIndicator } from './ProgressIndicator';
|
||||||
import { useKeywordCloud } from '@/hooks/useKeywordCloudClient';
|
import { useKeywordCloud } from '@/hooks/useKeywordCloudClient';
|
||||||
|
|
||||||
export function KeywordCloud({ initialQuarter }: { initialQuarter: string }) {
|
interface KeywordCloudProps {
|
||||||
|
initialQuarter: string;
|
||||||
|
locale: string;
|
||||||
|
texts: {
|
||||||
|
loading: string;
|
||||||
|
loadFailed: string;
|
||||||
|
retry: string;
|
||||||
|
hotKeyword: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function KeywordCloud({ initialQuarter, locale, texts }: KeywordCloudProps) {
|
||||||
const [currentQuarter, setCurrentQuarter] = useState(initialQuarter);
|
const [currentQuarter, setCurrentQuarter] = useState(initialQuarter);
|
||||||
const [quarters, setQuarters] = useState<string[]>([]);
|
const [quarters, setQuarters] = useState<string[]>([]);
|
||||||
const { data, isLoading, error } = useKeywordCloud(currentQuarter);
|
const { data, isLoading, error } = useKeywordCloud(currentQuarter);
|
||||||
@@ -41,7 +52,7 @@ export function KeywordCloud({ initialQuarter }: { initialQuarter: string }) {
|
|||||||
<div className="flex items-center justify-center min-h-[400px]">
|
<div className="flex items-center justify-center min-h-[400px]">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<div className="inline-block animate-spin rounded-full h-12 w-12 border-4 border-black border-t-primary mb-4" />
|
<div className="inline-block animate-spin rounded-full h-12 w-12 border-4 border-black border-t-primary mb-4" />
|
||||||
<p className="font-display font-bold text-lg">加载中...</p>
|
<p className="font-display font-bold text-lg">{texts.loading}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -52,13 +63,13 @@ export function KeywordCloud({ initialQuarter }: { initialQuarter: string }) {
|
|||||||
<div className="flex items-center justify-center min-h-[400px]">
|
<div className="flex items-center justify-center min-h-[400px]">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="font-display font-bold text-lg text-red-500 mb-4">
|
<p className="font-display font-bold text-lg text-red-500 mb-4">
|
||||||
加载失败
|
{texts.loadFailed}
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button
|
||||||
onClick={() => window.location.reload()}
|
onClick={() => window.location.reload()}
|
||||||
className="bg-primary text-black border-2 border-black px-4 py-2 font-bold hover:bg-yellow-500 transition-colors"
|
className="bg-primary text-black border-2 border-black px-4 py-2 font-bold hover:bg-yellow-500 transition-colors"
|
||||||
>
|
>
|
||||||
重试
|
{texts.retry}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,7 +98,7 @@ export function KeywordCloud({ initialQuarter }: { initialQuarter: string }) {
|
|||||||
<div className="relative py-12 px-4">
|
<div className="relative py-12 px-4">
|
||||||
<div className="word-cluster flex flex-wrap gap-3 items-center justify-center max-w-full">
|
<div className="word-cluster flex flex-wrap gap-3 items-center justify-center max-w-full">
|
||||||
{data.keywords.map((keyword) => (
|
{data.keywords.map((keyword) => (
|
||||||
<CloudWord key={keyword.id} data={keyword} />
|
<CloudWord key={keyword.id} data={keyword} locale={locale} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -95,7 +106,7 @@ export function KeywordCloud({ initialQuarter }: { initialQuarter: string }) {
|
|||||||
{/* 装饰元素 */}
|
{/* 装饰元素 */}
|
||||||
{data.keywords.length > 0 && firstKeyword && (
|
{data.keywords.length > 0 && firstKeyword && (
|
||||||
<div className="absolute -top-16 -right-8 md:right-0 bg-black text-white p-5 rounded-xl text-sm md:text-base font-display w-64 shadow-hard rotate-6 hidden lg:block">
|
<div className="absolute -top-16 -right-8 md:right-0 bg-black text-white p-5 rounded-xl text-sm md:text-base font-display w-64 shadow-hard rotate-6 hidden lg:block">
|
||||||
“{firstKeyword.word}” 是本季度最热门词汇!
|
{texts.hotKeyword.replace('{word}', firstKeyword.word)}
|
||||||
<div className="absolute -bottom-2 left-1/2 -translate-x-1/2 w-4 h-4 bg-black transform rotate-45" />
|
<div className="absolute -bottom-2 left-1/2 -translate-x-1/2 w-4 h-4 bg-black transform rotate-45" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { KeywordCloud } from './components/KeywordCloud';
|
import { KeywordCloud } from './components/KeywordCloud';
|
||||||
|
import { getTranslations } from 'next-intl/server';
|
||||||
|
|
||||||
interface PageProps {
|
interface PageProps {
|
||||||
params: {
|
params: {
|
||||||
@@ -9,27 +10,51 @@ interface PageProps {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function KeywordCloudPage({ searchParams }: PageProps) {
|
export async function generateMetadata({
|
||||||
|
params
|
||||||
|
}: {
|
||||||
|
params: Promise<{ locale: string }>
|
||||||
|
}) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations('keywordCloud');
|
||||||
|
|
||||||
|
return {
|
||||||
|
title: t('metaTitle'),
|
||||||
|
description: t('metaDescription'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function KeywordCloudPage({ searchParams, params }: PageProps) {
|
||||||
|
const { locale } = await params;
|
||||||
|
const t = await getTranslations('keywordCloud');
|
||||||
|
|
||||||
// 如果 URL 中有 quarter 参数,使用它;否则使用默认季度
|
// 如果 URL 中有 quarter 参数,使用它;否则使用默认季度
|
||||||
const quarter = searchParams.quarter || '2024-Q1';
|
const quarter = searchParams.quarter || '2024-Q1';
|
||||||
|
|
||||||
|
const texts = {
|
||||||
|
loading: t('loading'),
|
||||||
|
loadFailed: t('loadFailed'),
|
||||||
|
retry: t('retry'),
|
||||||
|
hotKeyword: t('hotKeyword'),
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="relative z-10 max-w-6xl mx-auto px-4 pb-32 overflow-visible">
|
<main className="relative z-10 max-w-6xl mx-auto px-4 pb-32 overflow-visible">
|
||||||
{/* 页面标题 */}
|
{/* 页面标题 */}
|
||||||
<header className="relative z-10 pt-16 pb-8 text-center max-w-4xl mx-auto px-4">
|
<header className="relative z-10 pt-16 pb-8 text-center max-w-4xl mx-auto px-4">
|
||||||
<div className="inline-block bg-accent dark:bg-purple-700 border-2 border-black px-3 py-1 font-display font-bold text-xs mb-4 shadow-hard-sm rotate-[-2deg]">
|
<div className="inline-block bg-accent dark:bg-purple-700 border-2 border-black px-3 py-1 font-display font-bold text-xs mb-4 shadow-hard-sm rotate-[-2deg]">
|
||||||
AI 热点追踪
|
{t('badge')}
|
||||||
</div>
|
</div>
|
||||||
<h1 className="font-display text-5xl md:text-7xl font-bold mb-6 tracking-tighter leading-tight">
|
<h1 className="font-display text-5xl md:text-7xl font-bold mb-6 tracking-tighter leading-tight">
|
||||||
季度 AI <span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-purple-500" style={{ WebkitTextStroke: '1.5px black' }}>热点词云</span>
|
{t('title')} <span className="text-transparent bg-clip-text bg-gradient-to-r from-blue-500 to-purple-500" style={{ WebkitTextStroke: '1.5px black' }}>{t('titleHighlight')}</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg md:text-xl max-w-2xl mx-auto font-medium text-gray-700 dark:text-gray-300 mb-8">
|
<p className="text-lg md:text-xl max-w-2xl mx-auto font-medium text-gray-700 dark:text-gray-300 mb-8">
|
||||||
从 “大型语言模型” 到 “Agent 工作流”。探索 AI 话语的演变历程。
|
{t('subtitle')}
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{/* 词云组件 */}
|
{/* 词云组件 */}
|
||||||
<KeywordCloud initialQuarter={quarter} />
|
<KeywordCloud initialQuarter={quarter} locale={locale} texts={texts} />
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,5 +95,17 @@
|
|||||||
"followUs": "Follow Us",
|
"followUs": "Follow Us",
|
||||||
"copyright": "© 2025 Agent Park. All rights reserved.",
|
"copyright": "© 2025 Agent Park. All rights reserved.",
|
||||||
"designedFor": "DESIGNED FOR AI BUILDERS"
|
"designedFor": "DESIGNED FOR AI BUILDERS"
|
||||||
|
},
|
||||||
|
"keywordCloud": {
|
||||||
|
"metaTitle": "AI Hotspot Word Cloud - Agent Park",
|
||||||
|
"metaDescription": "Explore the evolution of quarterly AI hotspots, from Large Language Models to Agent Workflows",
|
||||||
|
"badge": "AI Trend Tracker",
|
||||||
|
"title": "Quarterly AI",
|
||||||
|
"titleHighlight": "Hotspot Word Cloud",
|
||||||
|
"subtitle": "From “Large Language Models” to “Agent Workflows”. Explore the evolution of AI discourse.",
|
||||||
|
"loading": "Loading...",
|
||||||
|
"loadFailed": "Failed to load",
|
||||||
|
"retry": "Retry",
|
||||||
|
"hotKeyword": "“{word}” is the hottest keyword this quarter!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,5 +95,17 @@
|
|||||||
"followUs": "关注我们",
|
"followUs": "关注我们",
|
||||||
"copyright": "© 2025 Agent Park. 保留所有权利。",
|
"copyright": "© 2025 Agent Park. 保留所有权利。",
|
||||||
"designedFor": "专为 AI 构建者设计"
|
"designedFor": "专为 AI 构建者设计"
|
||||||
|
},
|
||||||
|
"keywordCloud": {
|
||||||
|
"metaTitle": "AI 热点词云 - Agent Park",
|
||||||
|
"metaDescription": "探索季度 AI 热点词汇的演变历程,从大型语言模型到 Agent 工作流",
|
||||||
|
"badge": "AI 热点追踪",
|
||||||
|
"title": "季度 AI",
|
||||||
|
"titleHighlight": "热点词云",
|
||||||
|
"subtitle": "从 “大型语言模型” 到 “Agent 工作流”。探索 AI 话语的演变历程。",
|
||||||
|
"loading": "加载中...",
|
||||||
|
"loadFailed": "加载失败",
|
||||||
|
"retry": "重试",
|
||||||
|
"hotKeyword": "“{word}” 是本季度最热门词汇!"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user