From af557c4146a6a014509f3665628a5fe93ec6590c Mon Sep 17 00:00:00 2001 From: xueweihan <595666367@qq.com> Date: Thu, 18 Jul 2024 16:45:23 +0800 Subject: [PATCH] fix: conflicts --- .gitignore | 1 + .vscode/settings.json | 2 +- next-i18next.config.js | 12 + next.config.js | 4 + package.json | 3 + public/locales/en/common.json | 52 ++++ public/locales/en/home.json | 30 +++ public/locales/en/repository.json | 99 ++++++++ public/locales/en/search.json | 6 + public/locales/zh/common.json | 52 ++++ public/locales/zh/home.json | 30 +++ public/locales/zh/repository.json | 99 ++++++++ public/locales/zh/search.json | 6 + src/components/buttons/LanguageSwitcher.tsx | 111 +++++++++ src/components/buttons/RankButton.tsx | 24 +- src/components/collection/AddCollection.tsx | 6 +- src/components/dialog/GroupItem.tsx | 13 +- src/components/dialog/TagModal.tsx | 58 +++-- src/components/home/Item.tsx | 230 ++++++++++++------ src/components/home/Items.tsx | 20 +- src/components/layout/About.tsx | 17 -- src/components/layout/Footer.tsx | 32 ++- src/components/layout/Header.tsx | 50 ++-- src/components/layout/Layout.tsx | 2 +- src/components/navbar/IndexBar.tsx | 15 +- src/components/navbar/RepoNavbar.tsx | 17 +- .../respository/CommentContainer.tsx | 27 +- src/components/respository/CommentItem.tsx | 49 ++-- src/components/respository/CommentSubmit.tsx | 70 +++--- src/components/respository/Info.tsx | 141 ++++++----- src/components/respository/MoreInfo.tsx | 29 ++- src/components/respository/Score.tsx | 8 +- src/components/respository/SvgTrend.tsx | 51 ++++ src/components/respository/Tabs.tsx | 17 +- src/components/search/SearchInput.tsx | 19 +- src/components/search/SearchResultItem.tsx | 80 ------ src/components/side/Ad.tsx | 53 ++-- src/components/side/Recommend.tsx | 14 +- src/components/side/Side.tsx | 29 ++- src/components/side/SideAd.tsx | 15 +- src/components/side/SideLoginButton.tsx | 8 +- src/components/side/Stats.tsx | 14 +- src/components/side/TagList.tsx | 25 +- src/components/side/UserStatus.tsx | 216 ++++++++-------- src/pages/_app.tsx | 5 +- src/pages/_document.tsx | 11 +- src/pages/index.tsx | 21 +- src/pages/repository/[rid]/index.tsx | 17 +- src/pages/search/result/index.tsx | 29 ++- src/types/home.tsx | 8 +- src/types/repository.tsx | 10 +- src/types/tag.tsx | 2 + src/types/user.ts | 5 - src/utils/day.ts | 9 +- yarn.lock | 77 +++++- 55 files changed, 1424 insertions(+), 626 deletions(-) create mode 100644 next-i18next.config.js create mode 100644 public/locales/en/common.json create mode 100644 public/locales/en/home.json create mode 100644 public/locales/en/repository.json create mode 100644 public/locales/en/search.json create mode 100644 public/locales/zh/common.json create mode 100644 public/locales/zh/home.json create mode 100644 public/locales/zh/repository.json create mode 100644 public/locales/zh/search.json create mode 100644 src/components/buttons/LanguageSwitcher.tsx delete mode 100644 src/components/layout/About.tsx create mode 100644 src/components/respository/SvgTrend.tsx delete mode 100644 src/components/search/SearchResultItem.tsx diff --git a/.gitignore b/.gitignore index 9fd495d4..c3030620 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ /node_modules /.pnp .pnp.js +pnpm-lock.yaml # testing /coverage diff --git a/.vscode/settings.json b/.vscode/settings.json index 0345f83d..5cc062f8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,7 @@ "editor.formatOnSave": false, "editor.tabSize": 2, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "headwind.runOnSave": false, "typescript.preferences.importModuleSpecifier": "non-relative", diff --git a/next-i18next.config.js b/next-i18next.config.js new file mode 100644 index 00000000..659b2333 --- /dev/null +++ b/next-i18next.config.js @@ -0,0 +1,12 @@ +//next-i18next.config.js + +module.exports = { + debug: true, + i18n: { + defaultLocale: 'zh', + locales: ['zh', 'en'], + }, + + // eslint-disable-next-line @typescript-eslint/no-var-requires + localePath: require('path').resolve('./public/locales'), // 指定翻译文件的路径 +}; diff --git a/next.config.js b/next.config.js index 532b97d4..38a9eab1 100644 --- a/next.config.js +++ b/next.config.js @@ -4,7 +4,11 @@ const withBundleAnalyzer = require('@next/bundle-analyzer')({ enabled: process.env.ANALYZE === 'true', }); +// eslint-disable-next-line @typescript-eslint/no-var-requires +const { i18n } = require('./next-i18next.config'); + module.exports = withBundleAnalyzer({ + i18n, eslint: { dirs: ['src'], }, diff --git a/package.json b/package.json index c6a0ad40..fdff335d 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,9 @@ "echarts": "^5.4.2", "echarts-for-react": "^3.0.2", "express": "^4.18.1", + "i18next": "^23.11.5", "next": "^12.1.6", + "next-i18next": "^15.3.0", "nprogress": "^0.2.0", "pino-http": "^8.2.1", "raw-loader": "^4.0.2", @@ -40,6 +42,7 @@ "rc-util": "^5.34.1", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-i18next": "^14.1.2", "react-icons": "^4.3.1", "react-infinite-scroll-hook": "^4.0.3", "react-markdown": "^8.0.3", diff --git a/public/locales/en/common.json b/public/locales/en/common.json new file mode 100644 index 00000000..5126562a --- /dev/null +++ b/public/locales/en/common.json @@ -0,0 +1,52 @@ +{ + "header": { + "search": "Search Open Source Projects", + "home": "Home", + "periodical": "Monthly", + "rank": "Ranking", + "article": "Articles", + "submit": "Submit" + }, + "user_side": { + "contribute_label": "Contributions", + "profile": "Profile", + "admin": "Settings", + "logout": "Sign Out", + "login": "Sign In" + }, + "advert": { + "desc": "Server expire in", + "desc2": "Buy me a coffee", + "next": "Need to gold", + "next2": "Next target by", + "day": "D", + "year": "Y" + }, + "site_stats": { + "user": "#Users", + "repo": "#Projects", + "title": "About", + "desc": "HelloGitHub is a platform to discover and share interesting, beginner-friendly open source projects.\nWe aim to help everyone find joy in programming, easily solve technical challenges,\nexplore amazing open-source tools, and naturally embark on their open-source journey." + }, + "recommend": { + "title": "Recommended", + "desc": "Content is collected from spdx and GitHub, following the", + "desc2": "protocol.", + "change": "Refresh" + }, + "footer": { + "feedback": "Feedback", + "business": "Business", + "contact": "Contact Us", + "agreement": "Agreement", + "source": "Source Code", + "sitemap": "Sitemap", + "server_sponsor": "Server is sponsored by", + "server_sponsor2": "", + "cdn_sponsor": "Cloud service is sponsored by", + "cdn_sponsor2": "" + }, + "collect": { + "title": "New" + } +} diff --git a/public/locales/en/home.json b/public/locales/en/home.json new file mode 100644 index 00000000..7dfe6233 --- /dev/null +++ b/public/locales/en/home.json @@ -0,0 +1,30 @@ +{ + "title": "Home", + "description": "Sharing interesting and beginner-friendly open-source projects on GitHub", + "bottom_text_login": "You have reached the bottom of the page", + "bottom_text_nologin": "End of the page! Sign in to read more", + "nav": { + "all": "All", + "featured": "Featured", + "tag": "Tags", + "submit": "Submit" + }, + "tag_side": { + "title": "Hot Tags", + "manage": "Preference" + }, + "tag_modal": { + "tips": "Tips: Click the tag on the left to 'Select', drag the selected tags on the right to 'Sort'", + "selected": "Selected: ", + "fetch_fail_msg": "Failed to fetch tags", + "add": "Add Tag", + "save": "Save", + "save_success_msg": "Save successful!", + "save_fail_msg": "Save failed!", + "max_tag_msg": "You can only select up to {{maxTotal}} tags!", + "编程语言": "Programming", + "技术栈": "Technology", + "应用类型": "Application", + "其它": "Other" + } +} diff --git a/public/locales/en/repository.json b/public/locales/en/repository.json new file mode 100644 index 00000000..fe024aca --- /dev/null +++ b/public/locales/en/repository.json @@ -0,0 +1,99 @@ +{ + "nav": { + "title": "Repository Details", + "desc": "Shared by", + "desc2": "" + }, + "info": { + "claimed": "Claimed", + "unclaim": "Unclaimed", + "score_desc": "HelloGitHub Rating", + "score_user_desc": "{{count}} ratings", + "vite": "Visit", + "voted": "Voted", + "vote": "Vote", + "opensource": "Free", + "discuss": "Discuss", + "share": "Share", + "collect": "Collect", + "copy_desc": "More details at:", + "copy_success": "Project information copied successfully, share it now!", + "copy_fail": "Copy failed" + }, + "url": { + "home": "Website", + "document": "Documentation", + "download": "Download", + "online": "Demo", + "source": "Source" + }, + "favorite": { + "default": "Default Favorites", + "cancel": "Unfavorite", + "success": "Added to Favorites", + "fail": "Failed to add to Favorites", + "title": "Select Favorites", + "desc": "You can find favorited projects in 'My Homepage'", + "save": "Save" + }, + "history": { + "past_day_desc": "Past {{days}} days", + "total_desc": "Received {{total}} stars ✨", + "fail_desc": "No star history data available" + }, + "more": { + "yes": "Yes", + "no": "No", + "null": "None", + "star": "Stars", + "chinese": "Chinese", + "language": "Language", + "activity": "Active", + "contributors": "Contributors", + "org": "Organization", + "version": "Latest", + "license": "License", + "expand": "More", + "collapse": "Collapse" + }, + "content": { + "desc_tab": "Description", + "code_tab": "Code", + "volume_label": "Included in:", + "volume": "Vol.{{volume}}", + "tag_label": "Tags:" + }, + "comment": { + "title": "Comments", + "sort_hot": "Hot", + "sort_new": "Newest", + "total": "{{total}} selected comments", + "more_reply": "View all {{total}} replies", + "load_more": "Load more...", + "no_comment": "No selected comments yet", + "used": "Used", + "unused": "Not Used", + "score": "Rating:", + "reply": "Reply", + "cancel": "Cancel", + "item": { + "featured": "Featured", + "unfeatured": "Not Featured", + "vote": "Vote", + "expand": "Expand", + "collapse": "Collapse", + "login": "Please log in first" + }, + "submit": { + "reply_placeholder": "Replying to: {{nickname}}", + "placeholder": "Write your comment: share your experience, pros/cons, use cases, or any surprising aspects of this open-source project...", + "save": "Submit", + "success": "Posted successfully! Will be displayed after approval", + "fail": "Submission failed", + "err1": "Comment content cannot be empty", + "err2": "Comment content must be at least 5 characters", + "err3": "Comment content cannot exceed 500 characters", + "err4": "Please provide a rating" + } + } +} diff --git a/public/locales/en/search.json b/public/locales/en/search.json new file mode 100644 index 00000000..6b0b4750 --- /dev/null +++ b/public/locales/en/search.json @@ -0,0 +1,6 @@ +{ + "title": "Open Source Project Search Results", + "description": "Find interesting and beginner-friendly open source projects on HelloGitHub", + "bottom_text_nologin": "That's all! Log in to see more content", + "navbar": "Search Results" +} diff --git a/public/locales/zh/common.json b/public/locales/zh/common.json new file mode 100644 index 00000000..e4435d66 --- /dev/null +++ b/public/locales/zh/common.json @@ -0,0 +1,52 @@ +{ + "header": { + "search": "搜索开源项目", + "home": "首页", + "periodical": "月刊", + "rank": "榜单", + "article": "文章", + "submit": "提交项目" + }, + "user_side": { + "contribute_label": "贡献值", + "profile": "我的主页", + "admin": "管理中心", + "logout": "退出登录", + "login": "立即登录" + }, + "advert": { + "desc": "服务器还剩", + "desc2": "微信扫码赞助本站", + "next": "距离目标还差", + "next2": "距离下个目标还差", + "day": "天", + "year": "年" + }, + "site_stats": { + "user": "用户总数", + "repo": "开源项目", + "title": "关于本站", + "desc": "HelloGitHub 是一个发现和分享有趣、入门级开源项目的平台。\n希望大家能够在这里找到编程的快乐、 轻松搞定问题的技术方案、\n大呼过瘾的开源神器, 顺其自然地开启开源之旅。" + }, + "recommend": { + "title": "推荐项目", + "desc": "内容整理自 spdx 和 GitHub 网站,并遵循", + "desc2": "协议。", + "change": "换一换" + }, + "footer": { + "feedback": "问题反馈", + "business": "商务合作", + "contact": "联系我们", + "agreement": "用户协议", + "source": "社区源码", + "sitemap": "站点地图", + "server_sponsor": "服务器由", + "server_sponsor2": "提供", + "cdn_sponsor": "专业的", + "cdn_sponsor2": "提供云存储服务" + }, + "collect": { + "title": "新建收藏夹" + } +} diff --git a/public/locales/zh/home.json b/public/locales/zh/home.json new file mode 100644 index 00000000..1437e619 --- /dev/null +++ b/public/locales/zh/home.json @@ -0,0 +1,30 @@ +{ + "title": "首页", + "description": "分享 GitHub 上有趣、入门级的开源项目", + "bottom_text_login": "你不经意间触碰到了底线", + "bottom_text_nologin": "到底啦!登录可查看更多内容", + "nav": { + "all": "全部", + "featured": "精选", + "tag": "标签", + "submit": "提交" + }, + "tag_side": { + "title": "热门标签", + "manage": "管理标签" + }, + "tag_modal": { + "tips": "操作提示:点击左侧标签为「选择」,拖拽右侧已选标签可「排序」", + "selected": "已选:", + "fetch_fail_msg": "获取标签失败", + "add": "添加标签", + "save": "保存", + "save_success_msg": "保存成功!", + "save_fail_msg": "保存失败!", + "max_tag_msg": "最多只能选择 {{maxTotal}} 个标签!", + "编程语言": "编程语言", + "技术栈": "技术栈", + "应用类型": "应用类型", + "其它": "其它" + } +} diff --git a/public/locales/zh/repository.json b/public/locales/zh/repository.json new file mode 100644 index 00000000..aff3d37f --- /dev/null +++ b/public/locales/zh/repository.json @@ -0,0 +1,99 @@ +{ + "nav": { + "title": "项目详情", + "desc": "由", + "desc2": "分享" + }, + "info": { + "claimed": "已认领", + "unclaim": "未认领", + "score_desc": "HelloGitHub 评分", + "score_user_desc": "{{count}} 人评分", + "vite": "访问", + "voted": "已赞", + "vote": "点赞", + "opensource": "开源", + "discuss": "讨论", + "share": "分享", + "collect": "收藏", + "copy_desc": "更多详情尽在:", + "copy_success": "项目信息已复制,快去分享吧!", + "copy_fail": "复制失败" + }, + "url": { + "home": "官网", + "document": "文档", + "download": "下载", + "online": "演示", + "source": "源码" + }, + "favorite": { + "default": "默认收藏夹", + "cancel": "取消收藏", + "success": "收藏成功", + "fail": "收藏失败", + "title": "选择收藏夹", + "desc": "收藏的项目在「我的主页」可以找到", + "save": "确定" + }, + "history": { + "past_day_desc": "过去 {{days}} 天", + "total_desc": "共收获 {{total}} 颗 Star ✨", + "fail_desc": "暂无 Star 历史数据" + }, + "more": { + "yes": "是", + "no": "否", + "null": "无", + "star": "星数", + "chinese": "中文", + "language": "主语言", + "activity": "活跃", + "contributors": "贡献者", + "org": "组织", + "version": "最新版本", + "license": "协议", + "expand": "更多", + "collapse": "收起" + }, + "content": { + "desc_tab": "介绍", + "code_tab": "代码", + "volume_label": "收录于:", + "volume": "第 {{volume}} 期", + "tag_label": "标签:" + }, + "comment": { + "title": "评论", + "sort_hot": "热门", + "sort_new": "最新", + "total": "{{total}} 条精选评论", + "more_reply": "查看全部 {{total}} 条回复", + "load_more": "加载更多...", + "no_comment": "暂无精选评论", + "used": "用过", + "unused": "没用过", + "score": "评分:", + "reply": "回复", + "cancel": "取消", + "item": { + "featured": "精选", + "unfeatured": "未精选", + "vote": "点赞", + "expand": "展开", + "collapse": "收起", + "login": "请先登录" + }, + "submit": { + "reply_placeholder": "正在回复:{{nickname}}", + "placeholder": "写下你的评论:分享开源项目的使用体验、优点/吐槽、适用场景、惊艳之处...", + "save": "发表", + "success": "发布成功!通过审核后展示", + "fail": "提交失败", + "err1": "评论内容不能为空", + "err2": "评论内容不能少于 5 个字", + "err3": "评论内容不能超过 500 个字", + "err4": "请评分" + } + } +} diff --git a/public/locales/zh/search.json b/public/locales/zh/search.json new file mode 100644 index 00000000..b2f686cc --- /dev/null +++ b/public/locales/zh/search.json @@ -0,0 +1,6 @@ +{ + "title": "开源项目搜索结果", + "description": "找有趣、入门级的开源项目就上 HelloGitHub", + "bottom_text_nologin": "到底啦!登录可查看更多内容", + "navbar": "搜索结果" +} diff --git a/src/components/buttons/LanguageSwitcher.tsx b/src/components/buttons/LanguageSwitcher.tsx new file mode 100644 index 00000000..f7e041e5 --- /dev/null +++ b/src/components/buttons/LanguageSwitcher.tsx @@ -0,0 +1,111 @@ +// components/LanguageSwitcher.tsx +import { useRouter } from 'next/router'; +import { useEffect, useRef, useState } from 'react'; + +const LanguageSwitcher = () => { + const router = useRouter(); + const { locale, asPath } = router; + const [selectedLocale, setSelectedLocale] = useState(locale); + const [isOpen, setIsOpen] = useState(false); + const dropdownRef = useRef(null); + + useEffect(() => { + const storedLocale = localStorage.getItem('locale'); + if (storedLocale && storedLocale !== locale) { + setSelectedLocale(storedLocale); + router.push(asPath, asPath, { locale: storedLocale }); + } + }, []); + + useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if ( + dropdownRef.current && + !dropdownRef.current.contains(event.target as Node) + ) { + setIsOpen(false); + } + }; + + document.addEventListener('mousedown', handleClickOutside); + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [dropdownRef]); + + const changeLanguage = (language: string) => { + localStorage.setItem('locale', language); + setSelectedLocale(language); + setIsOpen(false); // 关闭下拉菜单 + router.push(asPath, asPath, { locale: language }); + }; + + return ( +
+
+ +
+ + {isOpen && ( +
+
+ + + +
+
+ )} +
+ ); +}; + +export default LanguageSwitcher; diff --git a/src/components/buttons/RankButton.tsx b/src/components/buttons/RankButton.tsx index c491c268..79abcafd 100644 --- a/src/components/buttons/RankButton.tsx +++ b/src/components/buttons/RankButton.tsx @@ -5,24 +5,26 @@ import type { option } from '@/components/dropdown/Dropdown'; import Dropdown from '@/components/dropdown/Dropdown'; type RankButtonProps = { + t: (key: string) => string; type?: '' | 'dropdown'; }; -const btnList: option[] = [ - { key: '/', value: '首页' }, - { key: '/periodical', value: '月刊' }, - { key: '/report/tiobe', value: '榜单' }, - { key: '/article', value: '文章' }, - { key: '/onefile', value: 'OneFile' }, -]; - -const RankButton = (props: RankButtonProps) => { +const RankButton = ({ t, type = '' }: RankButtonProps) => { const router = useRouter(); + + const btnList: option[] = [ + { key: '/', value: t('header.home') }, + { key: '/periodical', value: t('header.periodical') }, + { key: '/report/tiobe', value: t('header.rank') }, + { key: '/article', value: t('header.article') }, + { key: '/onefile', value: 'OneFile' }, + ]; + const onChange = async (opt: option) => { router.push(opt.key as any); }; - if (props.type === 'dropdown') { + if (type === 'dropdown') { let key = '/'; if (router.isReady) { if (router.pathname.includes('periodical')) { @@ -47,7 +49,7 @@ const RankButton = (props: RankButtonProps) => { return ( 🏆 - 榜单 + {t('header.rank')} ); }; diff --git a/src/components/collection/AddCollection.tsx b/src/components/collection/AddCollection.tsx index b0be0aed..828b2c06 100644 --- a/src/components/collection/AddCollection.tsx +++ b/src/components/collection/AddCollection.tsx @@ -1,3 +1,4 @@ +import { useTranslation } from 'next-i18next'; import * as React from 'react'; import { AiOutlinePlus } from 'react-icons/ai'; @@ -223,6 +224,7 @@ export default function AddCollection({ className, onFinish, }: AddCollectionProps) { + const { t } = useTranslation('common'); const [openModal, setOpenModal] = React.useState(false); return ( @@ -234,12 +236,12 @@ export default function AddCollection({ }} className='inline-flex items-center justify-center gap-2 rounded-md border border-transparent py-1 px-0 text-sm font-medium text-blue-500 ring-offset-white transition-all hover:text-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2' > - 新建收藏夹 + {t('collect.title')} {/* 弹窗 */} setOpenModal(false)} diff --git a/src/components/dialog/GroupItem.tsx b/src/components/dialog/GroupItem.tsx index 90e83a18..0568df33 100644 --- a/src/components/dialog/GroupItem.tsx +++ b/src/components/dialog/GroupItem.tsx @@ -11,11 +11,18 @@ interface IProps { groupName: string; portalTagGroupsRef: MutableRefObject; handleAddTag: (tid: string) => void; + t: (key: string, total?: any) => string; } const GroupItem = (props: IProps) => { - const { item, effectedTidList, groupName, portalTagGroupsRef, handleAddTag } = - props; + const { + t, + item, + effectedTidList, + groupName, + portalTagGroupsRef, + handleAddTag, + } = props; const sortItemRef = useRef(null); useEffect(() => { @@ -27,7 +34,7 @@ const GroupItem = (props: IProps) => { onChoose: function ({ item }) { if (effectedTidList.length >= maxTotal) { item.draggable = false; - Message.error(`最多只能选择 ${maxTotal} 个标签`); + Message.error(t('tag_modal.max_tag_msg', { maxTotal: maxTotal })); return false; } }, diff --git a/src/components/dialog/TagModal.tsx b/src/components/dialog/TagModal.tsx index dddddef8..08002456 100644 --- a/src/components/dialog/TagModal.tsx +++ b/src/components/dialog/TagModal.tsx @@ -1,3 +1,4 @@ +import { TFunction } from 'i18next'; import { useCallback, useEffect, useRef, useState } from 'react'; import { IoIosCloseCircleOutline, @@ -19,14 +20,16 @@ import BasicDialog from '../dialog/BasicDialog'; import { maxTotal, PortalTag, PortalTagGroup } from '@/types/tag'; -const defaultTag = { name: '综合', tid: '', icon_name: 'find' }; - export function TagModal({ children, updateTags, + t, + i18n_lang, }: { children: JSX.Element; updateTags: any; + t: TFunction; + i18n_lang: string; }) { const [loading, setLoading] = useState(false); const [total, setTotal] = useState(0); @@ -48,11 +51,29 @@ export function TagModal({ } else { const res = await getSelectTags(); if (res.success) { - setPortalTagGroups(res.data); + const tagGroups = res.data.map((group) => { + const updatedTags = group.tags.map((tag) => { + // Replace the 'name' value with the 'name_en' value if available + if (i18n_lang == 'en' && tag.name_en !== null) { + return { + ...tag, + name: tag.name_en, + }; + } + return tag; + }); + + return { + ...group, + tags: updatedTags, + }; + }); + + setPortalTagGroups(tagGroups); setEffectedTidList(res.effected); setIsOpen(true); } else { - Message.error('获取标签失败'); + Message.error(t('tag_modal.fetch_error_msg')); } } }; @@ -84,27 +105,30 @@ export function TagModal({ const saveTags = async () => { if (effectedTidList.length <= maxTotal) { - const selectTags = []; - selectTags.unshift(defaultTag); + const defaultTag = + i18n_lang === 'en' + ? { name: 'All', tid: '', icon_name: 'find', name_en: 'All' } + : { name: '综合', tid: '', icon_name: 'find', name_en: 'All' }; + const selectTags = [defaultTag]; for (let i = 0; i < effectedTidList.length; i++) { const item = portalTagGroups .map((m) => m.tags) .flat() .find((f) => f.tid === effectedTidList[i]); - selectTags.push(item); + item && selectTags.push(item); } setLoading(true); const res = await saveSelectTags(effectedTidList); if (res.success) { - Message.success('保存成功!'); + Message.success(t('tag_modal.save_success_msg')); updateTags(selectTags); setIsOpen(false); } else { - Message.error(('保存失败:' + res?.message) as string); + Message.error(t('tag_modal.save_fail_msg')); } setLoading(false); } else { - Message.error(`最多只能选择 ${maxTotal} 个标签`); + Message.error(t('tag_modal.max_tag_msg', { maxTotal: maxTotal })); } }; @@ -142,9 +166,7 @@ export function TagModal({ >
- - 操作提示:点击左侧标签为「选择」,拖拽右侧已选标签可「排序」 - + {t('tag_modal.tips')}
@@ -153,7 +175,7 @@ export function TagModal({ key={'group_' + index} className='text-lg font-medium text-gray-500 dark:text-gray-200' > - {group.group_name} + {t(`${group.group_name}`)}
,
))}
, @@ -175,7 +198,8 @@ export function TagModal({
- 已选:{total} + {t('tag_modal.selected')} + {total} / {maxTotal}
@@ -230,7 +254,7 @@ export function TagModal({
- 添加标签 + {t('tag_modal.add')}
@@ -242,7 +266,7 @@ export function TagModal({ isLoading={loading} onClick={saveTags} > - 保存 + {t('tag_modal.save')} diff --git a/src/components/home/Item.tsx b/src/components/home/Item.tsx index 88af0ad5..204b49d5 100644 --- a/src/components/home/Item.tsx +++ b/src/components/home/Item.tsx @@ -1,19 +1,53 @@ -import { NextPage } from 'next'; +import Link from 'next/link'; import { AiFillFire, AiOutlineEye } from 'react-icons/ai'; -import { GoVerified } from 'react-icons/go'; +import { GoStar, GoVerified } from 'react-icons/go'; import CustomLink from '@/components/links/CustomLink'; import { fromNow } from '@/utils/day'; import { numFormat } from '@/utils/util'; -import { ItemProps } from '@/types/home'; +interface ItemProps { + item: { + item_id?: string; + rid?: string; + author_avatar: string; + is_hot?: boolean; + is_featured?: boolean; + is_claimed?: boolean; + name: string; + title: string; + comment_total?: number; + summary?: string; + author: string; + lang_color: string; + primary_lang: string; + publish_at?: string; + updated_at?: string; + clicks_total?: number; + stars?: number; + }; + i18n_lang: string; + index?: number; + showCommentCount?: boolean; + showViewCount?: boolean; + linkType?: 'default' | 'custom'; +} -const Item: NextPage = ({ item }) => { +const RepositoryItem = ({ + item, + index, + i18n_lang, + showCommentCount = false, + showViewCount = false, + linkType = 'default', +}: ItemProps) => { const { item_id, + rid, author_avatar, is_hot, + is_featured, is_claimed, name, title, @@ -22,120 +56,180 @@ const Item: NextPage = ({ item }) => { author, lang_color, primary_lang, + publish_at, updated_at, clicks_total, + stars, } = item; - if (!item_id || !name || !title) { - console.warn('Missing essential item data:', item); - return null; - } + const LinkComponent = linkType === 'custom' ? CustomLink : Link; + const href = + linkType === 'custom' ? `/repository/${item_id}` : `/repository/${rid}`; + const showDatetime = showCommentCount ? '' : 'hidden md:inline'; + + // 动态绑定类名 + const ClassName = `transform shadow-lg transition-transform ${ + showViewCount ? 'hover:scale-105' : '' + }`; return (
- {' '} - {/* 父容器防止溢出 */} -
- +
+
+ {/* 项目序号和标题 */} + {index !== undefined && ( +
+
+ + {index + 1} + . + {title} + + {/* 是否推荐 */} + {is_featured && ( +
+ {' '} + {i18n_lang == 'en' ? 'Featured' : '推荐'} +
+ )} +
+
+ )}
-
- {`${author} -
+ {/* 作者头像 */} + {showViewCount && ( +
+ {`${author} +
+ )} + {/* 项目介绍 */}

+ {/* 热点图标 */} {is_hot && ( )} -

- - {name} - - - — - - - {title} - -

+ {/* 项目标题和简介 */} + {showViewCount && ( +

+ + {name} + + + — + + + {title} + +

+ )}
-
- {comment_total > 0 && ( + {/* 评论数 */} + {showCommentCount && (comment_total || 0) > 0 && ( +
{comment_total}
- )} -
+
+ )}

+ {/* 项目描述 */}
{summary || '-'}
+ {/* 技术栈、认证、时间 */}
- {`${author} -
+ {linkType !== 'custom' && ( +
+ + + {author} + + · +
+ )} + {showViewCount && ( + {`${author} + )} +
{is_claimed && ( )} - {author} +
{name}
· -
-
+ -
- {primary_lang} -
-
- · - -
-
- - - {numFormat(clicks_total, 1, 1000)} + + {primary_lang.split(' ')[0]} + + + + + · +
+ {/* 项目 star 数 */} + {showViewCount && clicks_total !== undefined && ( +
+ + + {numFormat(clicks_total, 1, 1000)} + +
+ )} + {/* 项目查看数 */} + {stars && ( +
+ ✨Star {numFormat(stars, 1, 1000)} +
+ )}
- +
); }; -export default Item; +export default RepositoryItem; diff --git a/src/components/home/Items.tsx b/src/components/home/Items.tsx index 670b687a..9881c154 100644 --- a/src/components/home/Items.tsx +++ b/src/components/home/Items.tsx @@ -1,16 +1,26 @@ -import { NextPage } from 'next'; import React from 'react'; -import Item from './Item'; +import ProjectItem from '@/components/home/Item'; import { HomeItem } from '@/types/home'; -import { RepositoryItems } from '@/types/repository'; -const Items: NextPage = ({ repositories }) => { +type Props = { + repositories: HomeItem[]; + i18n_lang: string; +}; + +const Items = ({ repositories, i18n_lang }: Props) => { return (
{repositories.map((item: HomeItem) => ( - + ))}
); diff --git a/src/components/layout/About.tsx b/src/components/layout/About.tsx deleted file mode 100644 index e874605e..00000000 --- a/src/components/layout/About.tsx +++ /dev/null @@ -1,17 +0,0 @@ -// 本组件用于展示关于本站的介绍信息 - -const About: React.FC = () => { - return ( -
-
关于本站
- -
- HelloGitHub - 是一个分享有趣、入门级开源项目的平台。希望大家能够在这里找到编程的快乐、轻松搞定问题的技术方案、大呼过瘾的开源神器, - 一个偶遇的开源项目,开启你的开源之旅。 -
-
- ); -}; - -export default About; diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 8fd04719..5826226e 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -6,38 +6,48 @@ import CustomLink from '@/components/links/CustomLink'; import FooterLink from './FooterLink'; -const Footer = () => { +import { SideProps } from '@/types/home'; + +const Footer = ({ t }: SideProps) => { return (