返回 CodeWhale
page.tsx
根目录 / web / app / [locale] / plugins / page.tsx
1 import Link from "next/link";
2 import { buildPageMetadata } from "@/lib/page-meta";
3 import { PLUGINS_COPY, PLUGIN_COMMANDS } from "@/lib/content/plugins";
4 import type { LocalizedText } from "@/lib/content/vocabulary";
5 import { pickText } from "@/lib/i18n/dictionaries";
6
7 export const revalidate = 300;
8
9 const MARKETPLACE_REPO = "https://github.com/Hmbown/codewhale-plugin-marketplace";
10 const AUTHORING_DOC = "https://github.com/Hmbown/CodeWhale/blob/main/docs/PLUGIN_AUTHORING.md";
11
12 export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
13 const { locale } = await params;
14 return buildPageMetadata({ path: "/plugins", locale,
15 title: pickText(PLUGINS_COPY.metaTitle, locale),
16 description: pickText(PLUGINS_COPY.metaDescription, locale) });
17 }
18
19 export default async function PluginsPage({ params }: { params: Promise<{ locale: string }> }) {
20 const { locale } = await params;
21 const t = (copy: LocalizedText) => pickText(copy, locale);
22
23 return (
24 <div className="plugins-page">
25 <section className="hero">
26 <div className="portal-current" aria-hidden="true" />
27 <div className="portal-container community-welcome-inner">
28 <div className="eyebrow">{t(PLUGINS_COPY.kicker)}</div>
29 <h1>{t(PLUGINS_COPY.title)}</h1>
30 <p>{t(PLUGINS_COPY.lead)}</p>
31 <div className="portal-actions">
32 <Link href={MARKETPLACE_REPO} className="portal-button portal-button-primary">{t(PLUGINS_COPY.marketplaceCta)}</Link>
33 <Link href={AUTHORING_DOC} className="portal-button portal-button-secondary">{t(PLUGINS_COPY.docsCta)}</Link>
34 </div>
35 </div>
36 </section>
37
38 <section className="portal-section portal-section-muted" aria-labelledby="cu-title">
39 <div className="portal-container portal-section-grid">
40 <div className="portal-section-copy">
41 <span>{t(PLUGINS_COPY.cuLabel)}</span>
42 <h2 id="cu-title">{t(PLUGINS_COPY.cuTitle)}</h2>
43 <p>{t(PLUGINS_COPY.cuLead)}</p>
44 <p className="mt-4"><code className="font-mono text-[0.85rem]">{t(PLUGINS_COPY.cuInstall)}</code></p>
45 <p className="mt-3 text-ink-soft text-sm">{t(PLUGINS_COPY.cuToolsNote)}</p>
46 </div>
47 <dl className="folio-fact-list">
48 {PLUGINS_COPY.cuFeatures.map((feature) => (
49 <div key={t(feature.title)}>
50 <dt>{t(feature.title)}</dt>
51 <dd>{t(feature.detail)}</dd>
52 </div>
53 ))}
54 </dl>
55 </div>
56 </section>
57
58 <section className="portal-section" aria-labelledby="catalog-title">
59 <div className="portal-container portal-section-grid">
60 <div className="portal-section-copy">
61 <span>{t(PLUGINS_COPY.catalogLabel)}</span>
62 <h2 id="catalog-title">{t(PLUGINS_COPY.catalogTitle)}</h2>
63 <p>{t(PLUGINS_COPY.catalogLead)}</p>
64 <p className="mt-4"><code className="font-mono text-[0.85rem]">{t(PLUGINS_COPY.catalogBrowse)}</code></p>
65 </div>
66 <div className="portal-topic-list">
67 {PLUGINS_COPY.catalogEntries.map((entry) => (
68 <Link key={entry.reference} href={MARKETPLACE_REPO}>
69 <strong>{t(entry.title)}</strong>
70 <span>{t(entry.detail)}</span>
71 <span className="font-mono break-all">{entry.reference}</span>
72 </Link>
73 ))}
74 </div>
75 </div>
76 <div className="portal-container">
77 <p className="mt-6 text-ink-soft text-sm">{t(PLUGINS_COPY.catalogNote)}</p>
78 </div>
79 </section>
80
81 <section className="portal-section portal-section-muted" aria-labelledby="sources-title">
82 <div className="portal-container portal-section-grid">
83 <div className="portal-section-copy">
84 <span>{t(PLUGINS_COPY.sourcesLabel)}</span>
85 <h2 id="sources-title">{t(PLUGINS_COPY.sourcesTitle)}</h2>
86 <p>{t(PLUGINS_COPY.sourcesLead)}</p>
87 </div>
88 <div className="portal-topic-list">
89 {PLUGINS_COPY.sources.map((source) => (
90 <div key={source.reference}>
91 <strong>{t(source.title)}</strong>
92 <span>{t(source.detail)}</span>
93 <span className="font-mono break-all">{source.reference}</span>
94 </div>
95 ))}
96 </div>
97 </div>
98 </section>
99
100 <section className="portal-section" aria-labelledby="trust-title">
101 <div className="portal-container portal-section-grid">
102 <div className="portal-section-copy">
103 <span>{t(PLUGINS_COPY.trustLabel)}</span>
104 <h2 id="trust-title">{t(PLUGINS_COPY.trustTitle)}</h2>
105 <p>{t(PLUGINS_COPY.trustLead)}</p>
106 </div>
107 <div className="portal-topic-list">
108 {PLUGINS_COPY.trustSteps.map((step) => (
109 <div key={t(step.title)}>
110 <strong>{t(step.title)}</strong>
111 <span>{t(step.detail)}</span>
112 </div>
113 ))}
114 </div>
115 </div>
116 <div className="portal-container">
117 <p className="mt-6 text-ink-soft text-sm">{t(PLUGINS_COPY.trustNote)}</p>
118 </div>
119 </section>
120
121 <section className="portal-section portal-section-muted" aria-labelledby="commands-title">
122 <div className="portal-container">
123 <div className="portal-docs-heading">
124 <h2 id="commands-title">{t(PLUGINS_COPY.commandsTitle)}</h2>
125 <Link href={AUTHORING_DOC}>{t(PLUGINS_COPY.docsCta)}</Link>
126 </div>
127 <p className="mb-6 max-w-3xl text-ink-soft leading-relaxed">{t(PLUGINS_COPY.commandsLead)}</p>
128 <pre className="code-block text-[0.78rem]">{PLUGIN_COMMANDS.join("\n")}</pre>
129 </div>
130 </section>
131 </div>
132 );
133 }
134
134 lines Plain Text