feat: 剩余页面 useT() 改造 + 首页课程动态加载

- 搜索/发现/工具/My 页面全部使用 useT(),支持中英切换
- 搜索页 Badge variant 修正
- 首页热门专题改为从 API 动态获取(无数据时用静态兜底)
- 新增 discover 翻译键 viewCount/likeCount/postStats
- 首页/仪表盘/社区 等 JSX 优化
This commit is contained in:
yuzhiran-dev
2026-05-22 17:08:17 +08:00
parent 1b4c19daa6
commit 477226ac15
7 changed files with 97 additions and 127 deletions
+20 -36
View File
@@ -4,48 +4,34 @@ import { useEffect, useState } from 'react';
import Link from 'next/link';
import { apiFetch } from '@/lib/auth';
import { Skeleton } from '@/components/ui/skeleton';
import { useT } from '@/i18n';
interface HotItem {
id: number;
title: string;
viewCount: number;
likeCount: number;
_type: 'course' | 'prompt' | 'post';
}
interface HotItem { id: number; title: string; viewCount: number; likeCount: number; _type: 'course' | 'prompt' | 'post' }
export default function DiscoverPage() {
const t = useT();
const [hotCourses, setHotCourses] = useState<HotItem[]>([]);
const [hotPrompts, setHotPrompts] = useState<HotItem[]>([]);
const [hotPosts, setHotPosts] = useState<HotItem[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
loadData();
}, []);
useEffect(() => { loadData(); }, []);
async function loadData() {
try {
const [coursesRes, promptsRes, postsRes] = await Promise.all([
apiFetch('/courses?pageSize=5'),
apiFetch('/prompts?pageSize=5'),
apiFetch('/community/posts?pageSize=5'),
apiFetch('/courses?pageSize=5'), apiFetch('/prompts?pageSize=5'), apiFetch('/community/posts?pageSize=5'),
]);
const coursesData = await coursesRes.json();
const promptsData = await promptsRes.json();
const postsData = await postsRes.json();
setHotCourses((coursesData.items || []).map((c: any) => ({ ...c, _type: 'course' as const })));
setHotPrompts((promptsData.items || []).map((p: any) => ({ ...p, _type: 'prompt' as const })));
setHotPosts((postsData.items || []).map((p: any) => ({ ...p, _type: 'post' as const })));
setHotCourses((await coursesRes.json()).items?.map((c: any) => ({ ...c, _type: 'course' as const })) || []);
setHotPrompts((await promptsRes.json()).items?.map((p: any) => ({ ...p, _type: 'prompt' as const })) || []);
setHotPosts((await postsRes.json()).items?.map((p: any) => ({ ...p, _type: 'post' as const })) || []);
} catch (e) { console.error(e) }
setLoading(false);
}
if (loading) return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<Skeleton className="h-8 w-24 mb-2" />
<Skeleton className="h-5 w-64 mb-8" />
<Skeleton className="h-8 w-24 mb-2" /><Skeleton className="h-5 w-64 mb-8" />
{[1,2,3].map(i => <Skeleton key={i} className="h-32 rounded-xl mb-4" />)}
</div>
);
@@ -53,15 +39,13 @@ export default function DiscoverPage() {
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground"></h1>
<p className="mt-2 text-muted-foreground"></p>
<h1 className="text-3xl font-bold text-foreground">{t.discover.desc}</h1>
</div>
<div className="space-y-8">
<section>
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-foreground">🔥 </h2>
<Link href="/courses" className="text-sm text-brand-600 hover:underline"></Link>
<h2 className="text-xl font-semibold text-foreground">🔥 {t.discover.hotCourses}</h2>
<Link href="/courses" className="text-sm text-brand-600 hover:underline">{t.common.viewAll}</Link>
</div>
<div className="grid gap-3">
{hotCourses.map((item, index) => (
@@ -70,7 +54,7 @@ export default function DiscoverPage() {
<span className="text-2xl font-bold text-muted-foreground/20 w-8">{index + 1}</span>
<div className="flex-1">
<h3 className="font-medium text-foreground">{item.title}</h3>
<p className="text-xs text-muted-foreground mt-1">👁 {item.viewCount} </p>
<p className="text-xs text-muted-foreground mt-1">👁 {t.discover.viewCount?.replace('{n}', String(item.viewCount)) || `${item.viewCount} 浏览`}</p>
</div>
</Link>
))}
@@ -79,17 +63,17 @@ export default function DiscoverPage() {
<section>
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-foreground"> </h2>
<Link href="/prompts" className="text-sm text-brand-600 hover:underline"></Link>
<h2 className="text-xl font-semibold text-foreground"> {t.discover.hotPrompts}</h2>
<Link href="/prompts" className="text-sm text-brand-600 hover:underline">{t.common.viewAll}</Link>
</div>
<div className="grid gap-3">
{hotPrompts.map((item, index) => (
<Link key={item.id} href={`/prompts/${item.id}`}
<Link key={item.id} href={`/prompts`}
className="bg-card rounded-xl border border-border p-4 hover:shadow-md transition-shadow flex items-center gap-4">
<span className="text-2xl font-bold text-muted-foreground/20 w-8">{index + 1}</span>
<div className="flex-1">
<h3 className="font-medium text-foreground">{item.title}</h3>
<p className="text-xs text-muted-foreground mt-1"> {item.likeCount} </p>
<p className="text-xs text-muted-foreground mt-1"> {t.discover.likeCount?.replace('{n}', String(item.likeCount)) || `${item.likeCount} 点赞`}</p>
</div>
</Link>
))}
@@ -98,8 +82,8 @@ export default function DiscoverPage() {
<section>
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-foreground">💬 </h2>
<Link href="/community" className="text-sm text-brand-600 hover:underline"></Link>
<h2 className="text-xl font-semibold text-foreground">💬 {t.discover.hotPosts}</h2>
<Link href="/community" className="text-sm text-brand-600 hover:underline">{t.common.viewAll}</Link>
</div>
<div className="grid gap-3">
{hotPosts.map((item, index) => (
@@ -108,7 +92,7 @@ export default function DiscoverPage() {
<span className="text-2xl font-bold text-muted-foreground/20 w-8">{index + 1}</span>
<div className="flex-1">
<h3 className="font-medium text-foreground">{item.title}</h3>
<p className="text-xs text-muted-foreground mt-1"> {item.likeCount} · 👁 {item.viewCount} </p>
<p className="text-xs text-muted-foreground mt-1">{t.discover.postStats?.replace('{likes}', String(item.likeCount)).replace('{views}', String(item.viewCount)) || `❤️ ${item.likeCount} · 👁 ${item.viewCount}`}</p>
</div>
</Link>
))}
+16 -30
View File
@@ -1,60 +1,46 @@
'use client';
import { useEffect, useState } from 'react';
import Link from 'next/link';
import { apiFetch } from '../../lib/auth';
interface LearningItem {
courseId: number;
courseTitle: string;
progress: number;
completedLessons: number;
totalLessons: number;
}
import { useT } from '@/i18n';
export default function MyPage() {
const t = useT();
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground"></h1>
<p className="mt-2 text-muted-foreground"></p>
<h1 className="text-3xl font-bold text-foreground">{t.nav.my}</h1>
<p className="mt-2 text-muted-foreground">{t.my.desc}</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<Link href="/my/learning" className="bg-card rounded-xl border border-border p-6 hover:shadow-md transition-shadow block">
<div className="w-12 h-12 bg-brand-100 rounded-xl flex items-center justify-center text-brand-600 text-xl mb-4">📚</div>
<h3 className="font-semibold text-foreground mb-2"></h3>
<p className="text-sm text-muted-foreground"></p>
<h3 className="font-semibold text-foreground mb-2">{t.my.learningProgress}</h3>
<p className="text-sm text-muted-foreground">{t.my.learningProgressDesc}</p>
</Link>
<Link href="/my/favorites" className="bg-card rounded-xl border border-border p-6 hover:shadow-md transition-shadow block">
<div className="w-12 h-12 bg-red-100 rounded-xl flex items-center justify-center text-red-600 text-xl mb-4"></div>
<h3 className="font-semibold text-foreground mb-2"></h3>
<p className="text-sm text-muted-foreground"></p>
<h3 className="font-semibold text-foreground mb-2">{t.my.favorites}</h3>
<p className="text-sm text-muted-foreground">{t.my.favoritesDesc}</p>
</Link>
<Link href="/my/member" className="bg-card rounded-xl border border-border p-6 hover:shadow-md transition-shadow block">
<div className="w-12 h-12 bg-amber-100 rounded-xl flex items-center justify-center text-amber-600 text-xl mb-4">👑</div>
<h3 className="font-semibold text-foreground mb-2"></h3>
<p className="text-sm text-muted-foreground"></p>
<h3 className="font-semibold text-foreground mb-2">{t.my.memberCenter}</h3>
<p className="text-sm text-muted-foreground">{t.my.memberDesc}</p>
</Link>
<Link href="/my/settings" className="bg-card rounded-xl border border-border p-6 hover:shadow-md transition-shadow block">
<div className="w-12 h-12 bg-muted rounded-xl flex items-center justify-center text-muted-foreground text-xl mb-4"></div>
<h3 className="font-semibold text-foreground mb-2"></h3>
<p className="text-sm text-muted-foreground"></p>
<h3 className="font-semibold text-foreground mb-2">{t.my.settings}</h3>
<p className="text-sm text-muted-foreground">{t.my.settingsDesc}</p>
</Link>
<Link href="/learning/analytics" className="bg-card rounded-xl border border-border p-6 hover:shadow-md transition-shadow block">
<div className="w-12 h-12 bg-brand-100 rounded-xl flex items-center justify-center text-brand-600 text-xl mb-4">📊</div>
<h3 className="font-semibold text-foreground mb-2"></h3>
<p className="text-sm text-muted-foreground"></p>
<h3 className="font-semibold text-foreground mb-2">{t.learning.analytics}</h3>
<p className="text-sm text-muted-foreground">{t.my.analyticsDesc}</p>
</Link>
<Link href="/learning/path" className="bg-card rounded-xl border border-border p-6 hover:shadow-md transition-shadow block">
<div className="w-12 h-12 bg-green-100 rounded-xl flex items-center justify-center text-green-600 text-xl mb-4">🗺</div>
<h3 className="font-semibold text-foreground mb-2"></h3>
<p className="text-sm text-muted-foreground"> AI </p>
<h3 className="font-semibold text-foreground mb-2">{t.learning.path}</h3>
<p className="text-sm text-muted-foreground">{t.my.pathDesc}</p>
</Link>
</div>
</div>
+30 -10
View File
@@ -1,12 +1,22 @@
'use client';
import Link from 'next/link';
import { useState, useEffect } from 'react';
import { HomePageClient } from './home-client';
import { ArrowRight, Sparkles, BookOpen, Bot, Compass, Zap } from 'lucide-react';
import { useT } from '@/i18n';
import { API_BASE } from '@/lib/config';
export default function HomePage() {
const t = useT();
const [courses, setCourses] = useState<any[]>([]);
useEffect(() => {
fetch(`${API_BASE}/courses?pageSize=3`)
.then(r => r.json()).then(data => setCourses(data.items || []))
.catch(() => {});
}, []);
const stats = [
{ value: '50+', label: t.home.statTopics },
{ value: '200+', label: t.home.statPrompts },
@@ -107,25 +117,35 @@ export default function HomePage() {
</Link>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{[
{ title: 'AI 通识:零基础入门', students: '1,280', tag: t.courses.free, gradient: 'from-brand-500 to-blue-500', desc: '面向零基础用户,带你了解 AI 的基本概念、发展历程和实际应用。' },
{ title: '提示词工程从入门到精通', students: '860', tag: '热门', gradient: 'from-violet-500 to-purple-500', desc: '系统学习提示词编写技巧,掌握与 AI 高效沟通的方法。' },
{ title: '用 AI 提升 10 倍办公效率', students: '2,150', tag: '推荐', gradient: 'from-amber-500 to-orange-500', desc: '学习使用 AI 工具处理文档、数据分析、演示制作等日常工作。' },
].map((course) => (
{courses.length > 0 ? courses.map((course: any) => (
<Link key={course.id} href={`/courses/${course.id}`} className="block group">
<div className="group bg-card rounded-xl border border-border overflow-hidden hover:shadow-xl transition-all hover:-translate-y-1">
<div className="h-2 bg-gradient-to-r from-brand-500 to-blue-500" />
<div className="p-6">
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-medium text-brand-700 dark:text-brand-300 bg-brand-50 dark:bg-brand-900/30 px-2 py-1 rounded-full">{course.isFree ? t.courses.free : t.courses.paid}</span>
</div>
<h3 className="text-lg font-semibold mb-2 group-hover:text-brand-600 transition-colors">{course.title}</h3>
<p className="text-sm text-muted-foreground mb-4 line-clamp-2">{course.description || ''}</p>
</div>
</div>
</Link>
)) : (
<>
{[{ title: 'AI 通识:零基础入门', tag: t.courses.free }, { title: '提示词工程从入门到精通', tag: '热门' }, { title: '用 AI 提升 10 倍办公效率', tag: '推荐' }].map((course) => (
<div key={course.title} className="group bg-card rounded-xl border border-border overflow-hidden hover:shadow-xl transition-all hover:-translate-y-1">
<div className={`h-2 bg-gradient-to-r ${course.gradient}`} />
<div className="h-2 bg-gradient-to-r from-brand-500 to-blue-500" />
<div className="p-6">
<div className="flex items-center justify-between mb-3">
<span className="text-xs font-medium text-brand-700 dark:text-brand-300 bg-brand-50 dark:bg-brand-900/30 px-2 py-1 rounded-full">{course.tag}</span>
</div>
<h3 className="text-lg font-semibold mb-2 group-hover:text-brand-600 transition-colors">{course.title}</h3>
<p className="text-sm text-muted-foreground mb-4 line-clamp-2">{course.desc}</p>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<span className="flex items-center gap-1"><BookOpen className="w-4 h-4" />{t.home.moduleCount.replace('{n}', course.students)}</span>
</div>
<p className="text-sm text-muted-foreground mb-4 line-clamp-2"> AI </p>
</div>
</div>
))}
</>
)}
</div>
</div>
</section>
+14 -27
View File
@@ -9,12 +9,9 @@ import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { SearchIcon, FileText, BookOpen, Wrench, MessageSquare } from 'lucide-react';
import { API_BASE } from '@/lib/config';
import { useT } from '@/i18n';
interface SearchResult {
id: number; _type: 'course' | 'prompt' | 'tool' | 'content';
title?: string; name?: string; description?: string; summary?: string;
cover?: string; icon?: string; url?: string; model?: string; isFree?: boolean; publishedAt?: string;
}
interface SearchResult { id: number; _type: 'course' | 'prompt' | 'tool' | 'content'; title?: string; name?: string; description?: string; summary?: string; cover?: string; icon?: string; url?: string; model?: string; isFree?: boolean; publishedAt?: string }
export default function SearchPage() {
return (
@@ -25,6 +22,7 @@ export default function SearchPage() {
}
function SearchContent() {
const t = useT();
const searchParams = useSearchParams();
const q = searchParams.get('q') || '';
const type = searchParams.get('type') || 'all';
@@ -42,19 +40,18 @@ function SearchContent() {
}, [q, type]);
const groups = { course: results.filter(r => r._type === 'course'), prompt: results.filter(r => r._type === 'prompt'), tool: results.filter(r => r._type === 'tool'), content: results.filter(r => r._type === 'content') };
const groupLabels: Record<string, string> = { course: '专题', prompt: '提示词', tool: 'AI 工具', content: '文章' };
const groupLabels: Record<string, string> = { course: t.search.groupCourse, prompt: t.search.groupPrompt, tool: t.search.groupTool, content: t.search.groupContent };
const groupIcons: Record<string, any> = { course: BookOpen, prompt: MessageSquare, tool: Wrench, content: FileText };
const groupLinks: Record<string, string> = { course: '/courses/', prompt: '/prompts', tool: '/tools', content: '/contents/' };
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-8">
<h1 className="text-3xl font-bold text-foreground mb-4"></h1>
<h1 className="text-3xl font-bold text-foreground mb-4">{t.search.title}</h1>
<form onSubmit={e => { e.preventDefault(); window.location.href = `/search?q=${encodeURIComponent(input)}`; }}>
<div className="relative">
<SearchIcon className="absolute left-3 top-1/2 -translate-y-1/2 w-5 h-5 text-muted-foreground" />
<Input type="text" value={input} onChange={e => setInput(e.target.value)}
placeholder="搜索专题、提示词、工具、文章..." className="pl-10 h-12 text-base" />
<Input type="text" value={input} onChange={e => setInput(e.target.value)} placeholder={t.search.placeholder} className="pl-10 h-12 text-base" />
</div>
</form>
</div>
@@ -62,25 +59,23 @@ function SearchContent() {
{!q && (
<div className="text-center py-20 text-muted-foreground">
<SearchIcon className="w-12 h-12 mx-auto mb-4 opacity-30" />
<p></p>
<p>{t.search.emptyHint}</p>
</div>
)}
{q && loading && (
<div className="space-y-4">
{[1,2,3].map(i => <Skeleton key={i} className="h-24 w-full rounded-xl" />)}
</div>
<div className="space-y-4">{[1,2,3].map(i => <Skeleton key={i} className="h-24 w-full rounded-xl" />)}</div>
)}
{q && !loading && total === 0 && (
<div className="text-center py-20 text-muted-foreground">
<p> "<span className="text-foreground font-medium">{q}</span>" </p>
<p>{t.search.noResults.replace('{q}', q)}</p>
</div>
)}
{q && !loading && total > 0 && (
<div>
<p className="text-sm text-muted-foreground mb-6"> {total} </p>
<p className="text-sm text-muted-foreground mb-6">{t.search.resultsCount.replace('{n}', String(total))}</p>
<div className="space-y-8">
{Object.entries(groups).map(([key, items]) => {
if (items.length === 0) return null;
@@ -90,25 +85,17 @@ function SearchContent() {
<div className="flex items-center gap-2 mb-3">
<Icon className="w-4 h-4 text-muted-foreground" />
<span className="text-sm font-medium">{groupLabels[key]}</span>
<span className="text-xs text-muted-foreground">{items.length} </span>
<span className="text-xs text-muted-foreground">{items.length} {t.search.resultsCount.replace('{n}', '')}</span>
</div>
<div className="grid gap-3">
{items.map((item) => (
<Link key={`${key}-${item.id}`}
href={`${groupLinks[key]}${key === 'course' || key === 'content' ? item.id : ''}`}
className="block">
<Link key={`${key}-${item.id}`} href={`${groupLinks[key]}${key === 'course' || key === 'content' ? item.id : ''}`} className="block">
<Card className="p-4 hover:border-brand-200 dark:hover:border-brand-800 transition-colors">
<h3 className="font-semibold">{item.title || item.name}</h3>
<p className="text-sm text-muted-foreground mt-1 line-clamp-2">{item.description || item.summary}</p>
<div className="flex gap-2 mt-2">
{key === 'course' && (
<Badge variant={item.isFree ? 'success' : 'destructive'}>
{item.isFree ? '免费' : '付费'}
</Badge>
)}
{key === 'prompt' && item.model && (
<span className="text-xs text-muted-foreground">: {item.model}</span>
)}
{key === 'course' && <Badge variant={item.isFree ? 'secondary' : 'destructive'}>{item.isFree ? t.courses.free : t.courses.paid}</Badge>}
{key === 'prompt' && item.model && <span className="text-xs text-muted-foreground">: {item.model}</span>}
</div>
</Card>
</Link>
+8 -15
View File
@@ -6,26 +6,21 @@ import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { Wrench, ExternalLink, Star } from 'lucide-react';
import { API_BASE } from '@/lib/config';
import { useT } from '@/i18n';
interface Tool {
id: number; name: string; description: string; url: string;
icon: string | null; isFeatured: boolean; tags: string | null;
}
interface Tool { id: number; name: string; description: string; url: string; icon: string | null; isFeatured: boolean; tags: string | null }
function ToolSkeleton() {
return (
<Card className="p-5">
<div className="flex gap-2 mb-2">
<Skeleton className="h-5 w-12 rounded-full" />
<Skeleton className="h-5 w-12 rounded-full" />
</div>
<Skeleton className="h-5 w-1/2 mb-1" />
<Skeleton className="h-4 w-full" />
<div className="flex gap-2 mb-2"><Skeleton className="h-5 w-12 rounded-full" /><Skeleton className="h-5 w-12 rounded-full" /></div>
<Skeleton className="h-5 w-1/2 mb-1" /><Skeleton className="h-4 w-full" />
</Card>
);
}
export default function ToolsPage() {
const t = useT();
const [tools, setTools] = useState<Tool[]>([]);
const [loading, setLoading] = useState(true);
@@ -38,14 +33,12 @@ export default function ToolsPage() {
return (
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="mb-10">
<h1 className="text-3xl font-bold text-foreground">AI </h1>
<p className="mt-2 text-muted-foreground"> AI </p>
<h1 className="text-3xl font-bold text-foreground">{t.tools.title}</h1>
<p className="mt-2 text-muted-foreground">{t.tools.desc}</p>
</div>
{loading ? (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{[1,2,3,4,5,6].map(i => <ToolSkeleton key={i} />)}
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">{[1,2,3,4,5,6].map(i => <ToolSkeleton key={i} />)}</div>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{tools.map((tool) => (
+1 -1
View File
@@ -15,7 +15,7 @@ const en: Translations = {
courses: { title: 'Courses', desc: 'Explore AI systematically from beginner to expert', empty: 'No courses available', free: 'Free', paid: 'Paid', moduleCount: '{n} modules' },
search: { title: 'Search Results', placeholder: 'Search courses, prompts, tools, articles...', emptyHint: 'Enter keywords to search', noResults: 'No results found for "{q}"', resultsCount: '{n} results found', groupCourse: 'Courses', groupPrompt: 'Prompts', groupTool: 'AI Tools', groupContent: 'Articles' },
tools: { title: 'AI Tools', desc: 'Curated AI tools to boost your productivity' },
discover: { desc: 'Explore trending content and curated picks', hotCourses: 'Hot Courses', hotPrompts: 'Trending Prompts', hotPosts: 'Popular Discussions' },
discover: { desc: 'Explore trending content and curated picks', hotCourses: 'Hot Courses', hotPrompts: 'Trending Prompts', hotPosts: 'Popular Discussions', viewCount: '{n} views', likeCount: '{n} likes', postStats: '❤️ {likes} · 👁 {views}' },
footer: { tagline: 'Empowering Everyone to Master AI', explore: 'Explore', about: 'About', aboutUs: 'About Us', privacy: 'Privacy Policy', terms: 'Terms of Service', aiAgreement: 'AI Service Agreement', contact: 'Contact', copyright: '© {year} Yuzhiran Technology Center. All rights reserved.', models: 'Models', aiTools: 'AI Tools', articles: 'Articles', skills: 'Skills' },
models: { title: 'AI Model Encyclopedia', desc: 'Compare mainstream LLMs to find the best fit', tableName: 'Model', tableProvider: 'Provider', tableCapabilities: 'Capabilities', tableContext: 'Context', tableMaxOutput: 'Max Output', tablePricing: 'Pricing', free: 'Free', recommended: 'Recommended', pricingFree: 'Free', pricingMixed: 'Free/Paid', pricingPaid: 'Paid' },
error: { title: 'Something went wrong', desc: 'Page failed to load. Please try again.', reload: 'Reload' },
+1 -1
View File
@@ -13,7 +13,7 @@ const zh = {
courses: { title: '专题', desc: '系统化探索 AI,从入门到精通', empty: '暂无专题内容', free: '免费', paid: '付费', moduleCount: '{n} 模块' },
search: { title: '搜索结果', placeholder: '搜索专题、提示词、工具、文章...', emptyHint: '输入关键词搜索', noResults: '未找到与 "{q}" 相关的结果', resultsCount: '找到 {n} 个结果', groupCourse: '专题', groupPrompt: '提示词', groupTool: 'AI 工具', groupContent: '文章' },
tools: { title: 'AI 工具库', desc: '收录优质 AI 工具,助力工作效率提升' },
discover: { desc: '探索热门内容和精选推荐', hotCourses: '热门课程', hotPrompts: '热门提示词', hotPosts: '热门讨论' },
discover: { desc: '探索热门内容和精选推荐', hotCourses: '热门课程', hotPrompts: '热门提示词', hotPosts: '热门讨论', viewCount: '{n} 浏览', likeCount: '{n} 点赞', postStats: '❤️ {likes} 点赞 · 👁 {views} 浏览' },
footer: { tagline: '让每个人都能用好 AI', explore: '探索', about: '关于', aboutUs: '关于我们', privacy: '隐私政策', terms: '服务协议', aiAgreement: 'AI 服务协议', contact: '联系方式', copyright: '© {year} 北京宇之然科技中心 版权所有', models: '模型百科', aiTools: 'AI 工具', articles: '文章', skills: '技能' },
models: { title: 'AI 模型百科', desc: '收录主流大语言模型,全面对比各项参数', tableName: '模型名称', tableProvider: '提供商', tableCapabilities: '能力', tableContext: '上下文', tableMaxOutput: '最大输出', tablePricing: '价格', free: '免费', recommended: '推荐', pricingFree: '免费', pricingMixed: '免费/付费', pricingPaid: '付费' },
error: { title: '出错了', desc: '页面加载失败,请稍后重试', reload: '重新加载' },