diff --git a/public/links/friendly-links-hero.png b/public/links/friendly-links-hero.png index 19b5a81..67158bf 100644 Binary files a/public/links/friendly-links-hero.png and b/public/links/friendly-links-hero.png differ diff --git a/src/app/globals.css b/src/app/globals.css index fbeb7e6..9149f09 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -55,7 +55,7 @@ a { color: inherit; text-decoration: none; } .home-collage { position: relative; height: 648px; margin-top: 50px; }.home-collage-tile { position: absolute; border-radius: 13px; background: #d9d9d9; }.home-collage-wide { top: 0; left: 0; width: 41.4%; height: 359px; }.home-collage-medium { top: 0; left: 42.57%; width: 28.87%; height: 359px; }.home-collage-small { top: 376px; left: 27.28%; width: 44.3%; height: 272px; }.home-collage-bottom { top: 376px; left: 0; width: 25.83%; height: 272px; }.home-collage-tall { top: 0; right: 0; width: 27.1%; height: 648px; } .news-page { padding: 30px 0 60px; } -.friendly-links-hero { background: var(--policy-hero-image) center 26.71% / 100% auto no-repeat; } +.friendly-links-hero { background: var(--policy-hero-image) center 28.2% / 100% auto no-repeat; } .friendly-links-page-inner { gap: 50px; } .friendly-links-content { width: min(1214px, 100%); margin: 0 auto; } .friendly-links-section + .friendly-links-section { margin-top: 50px; } @@ -65,7 +65,8 @@ a { color: inherit; text-decoration: none; } a.friendly-link-card:hover, a.friendly-link-card:focus-visible { border-color: #8f56a2; box-shadow: 0 8px 20px rgba(40, 29, 47, .1); transform: translateY(-2px); } .friendly-link-card-content { display: flex; flex: 1; flex-direction: column; align-items: center; justify-content: center; gap: 16.42px; padding: 32.83px 10.94px; } .friendly-link-card-heading { display: flex; align-items: center; justify-content: center; gap: 16px; min-width: 0; font-size: 27.36px; font-weight: 400; line-height: normal; white-space: nowrap; } -.friendly-link-card-heading img { width: 65.67px; height: 65.67px; overflow: hidden; border-radius: 8px; object-fit: cover; } +.friendly-link-card-logo { display: flex; width: 65.67px; height: 65.67px; flex: 0 0 65.67px; align-items: center; justify-content: center; overflow: hidden; border-radius: 10.944px; background: #fff; } +.friendly-link-card-logo img { display: block; width: 65.67px; height: 65.67px; object-fit: cover; } .friendly-link-card-description { color: #8d949a; font-size: 16.42px; line-height: 21.89px; text-align: center; } .friendly-link-card-archive { position: absolute; top: 13px; right: 5px; padding: 4px 6px; border-radius: 4px; background: #e72329; color: #fff; font-size: 10px; line-height: 12px; } .friendly-links-page .page-bottom-nav { margin-top: 176px; } diff --git a/src/components/links/friendly-links-page.tsx b/src/components/links/friendly-links-page.tsx index 756a435..9002231 100644 --- a/src/components/links/friendly-links-page.tsx +++ b/src/components/links/friendly-links-page.tsx @@ -3,18 +3,69 @@ import type { CSSProperties } from "react"; import { PageBreadcrumb } from "@/components/layout/page-breadcrumb"; import { getLocaleMessages, type Locale } from "@/lib/i18n"; -type LinkEntry = { name: string; description: string; logo: string; url?: string; archived?: boolean }; -type LocalizedText = Record; -type LinkSection = { id: string; title: LocalizedText; links: LinkEntry[] }; +type LocalizedText = Partial>; +type LinkEntry = { + name: string; + description: string; + logo: string; + url?: string; + archived?: boolean; + i18n?: { name?: LocalizedText; description?: LocalizedText }; +}; +type LinkSection = { id: string; title: Record; links: LinkEntry[] }; + const linkSections = links.sections as LinkSection[]; -function FriendlyLinkCard({ link }: { link: LinkEntry }) { - const content = <>{link.name}{link.description}{link.archived && 已归档}; - return link.url ? {content} :
{content}
; +function localized(value: string, translations: LocalizedText | undefined, locale: Locale) { + return translations?.[locale] ?? value; +} + +function FriendlyLinkCard({ link, locale, visitLabel }: { link: LinkEntry; locale: Locale; visitLabel: string }) { + const name = localized(link.name, link.i18n?.name, locale); + const description = localized(link.description, link.i18n?.description, locale); + const content = ( + <> + + + + {name} + + {description} + + {link.archived && 已归档} + + ); + + return link.url ? ( + + {content} + + ) :
{content}
; } export function FriendlyLinksPage({ locale }: { locale: Locale }) { const messages = getLocaleMessages(locale); const heroStyle = { "--policy-hero-image": 'url("/links/friendly-links-hero.png")' } as CSSProperties; - return

{messages.footer.legal[2]}

{linkSections.map((section) =>
{section.links.map((link, index) => )}
)}
; + const visitLabels: Record = { "zh-Hans": "访问", "zh-Hant": "造訪", en: "Visit", jp: "訪問" }; + + return ( +
+
+
+

{messages.footer.legal[2]}

+
+
+ {linkSections.map((section) => ( +
+ +
+ {section.links.map((link, index) => )} +
+
+ ))} +
+
+
+
+ ); } diff --git a/src/data/links.json b/src/data/links.json index dbcd85f..e0bac62 100644 --- a/src/data/links.json +++ b/src/data/links.json @@ -4,9 +4,27 @@ "id": "services", "title": { "zh-Hans": "服务提供", "zh-Hant": "服務提供", "en": "Service providers", "jp": "サービス提供" }, "links": [ - { "name": "德威云", "description": "Minecraft 服务器提供方", "logo": "/links/fetarute.png", "url": "https://www.deviy.cn/" }, - { "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }, - { "name": "蓝色秋生", "description": "网页服务提供方,对象存储、CDN 维护方", "logo": "/links/blueakio.svg", "url": "https://www.blueakio.com/" } + { + "name": "德威云", + "description": "Minecraft 服务器提供方", + "logo": "/links/fetarute.png", + "url": "https://www.deviy.cn/", + "i18n": { "description": { "en": "Minecraft server provider", "jp": "Minecraft サーバー提供元" } } + }, + { + "name": "腾讯云", + "description": "CDN、对象存储提供方", + "logo": "/links/fetarute.png", + "url": "https://cloud.tencent.com/", + "i18n": { "description": { "en": "CDN and object storage provider", "jp": "CDN・オブジェクトストレージ提供元" } } + }, + { + "name": "蓝色秋生", + "description": "网页服务提供方,对象存储、CDN 维护方", + "logo": "/links/blueakio.svg", + "url": "https://www.blueakio.com/", + "i18n": { "description": { "en": "Web service, object storage, and CDN provider", "jp": "ウェブサービス、オブジェクトストレージ、CDN の提供元" } } + } ] }, { @@ -14,11 +32,11 @@ "title": { "zh-Hans": "朋友们", "zh-Hant": "朋友們", "en": "Friends", "jp": "友だち" }, "links": [ { "name": "德威云", "description": "Minecraft 服务器提供方", "logo": "/links/fetarute.png", "url": "https://deviy.cn/" }, - { "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }, - { "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }, - { "name": "蓝色秋生", "description": "网页服务提供方,对象存储、CDN 维护方", "logo": "/links/fetarute.png", "url": "https://liqiusheng.cn/" }, - { "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }, - { "name": "腾讯云", "description": "CDN、对象存储 提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" } + { "name": "腾讯云", "description": "CDN、对象存储提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }, + { "name": "腾讯云", "description": "CDN、对象存储提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }, + { "name": "蓝色秋生", "description": "网页服务提供方,对象存储、CDN 维护方", "logo": "/links/blueakio.svg", "url": "https://liqiusheng.cn/" }, + { "name": "腾讯云", "description": "CDN、对象存储提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" }, + { "name": "腾讯云", "description": "CDN、对象存储提供方", "logo": "/links/fetarute.png", "url": "https://cloud.tencent.com/" } ] } ]