}) => {
-
+
{t("page-10-year-innovation-title")}
-
+
{t("page-10-year-innovation-subtitle")}
@@ -292,10 +292,10 @@ const Page = async (props: { params: Promise
}) => {
-
+
{t("page-10-year-adoption-title")}
-
+
{t("page-10-year-adoption-subtitle")}
@@ -348,10 +348,10 @@ const Page = async (props: { params: Promise
}) => {
-
+
{t("page-10-year-stories-title")}
-
+
{t("page-10-year-stories-subtitle")}
diff --git a/app/[locale]/[...slug]/page-jsonld.tsx b/app/[locale]/[...slug]/page-jsonld.tsx
index a48a876b67e..32dfe10e803 100644
--- a/app/[locale]/[...slug]/page-jsonld.tsx
+++ b/app/[locale]/[...slug]/page-jsonld.tsx
@@ -2,14 +2,11 @@ import { FileContributor, Frontmatter } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+import { resolveAuthorsFromFrontmatter } from "@/lib/jsonld/utils"
+
export default async function SlugJsonLD({
locale,
slug,
@@ -29,7 +26,7 @@ export default async function SlugJsonLD({
"@type": "ListItem",
position: 1,
name: "Home",
- item: `https://ethereum.org/${locale}/`,
+ item: normalizeUrlForJsonLd(locale, "/"),
},
]
@@ -54,11 +51,15 @@ export default async function SlugJsonLD({
url: contributor.html_url,
}))
+ const { authorGraphNodes, authorIds } = resolveAuthorsFromFrontmatter(
+ frontmatter.authors ?? frontmatter.author
+ )
+
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
+ ...authorGraphNodes,
{
"@type": "WebPage",
"@id": url,
@@ -66,20 +67,15 @@ export default async function SlugJsonLD({
description: frontmatter.description,
url: url,
inLanguage: locale,
- author: [ethereumCommunityReference],
+ author: authorIds,
contributor: contributorList,
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: breadcrumbItems,
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
mainEntity: { "@id": `${url}#article` },
},
{
@@ -90,10 +86,9 @@ export default async function SlugJsonLD({
image: frontmatter.image
? `https://ethereum.org${frontmatter.image}`
: undefined,
- author: [ethereumCommunityReference],
+ author: authorIds,
contributor: contributorList,
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
dateModified: frontmatter.published,
mainEntityOfPage: url,
about: {
diff --git a/app/[locale]/apps/[application]/page-jsonld.tsx b/app/[locale]/apps/[application]/page-jsonld.tsx
index 9e7a33d39e2..bdb7ec1e395 100644
--- a/app/[locale]/apps/[application]/page-jsonld.tsx
+++ b/app/[locale]/apps/[application]/page-jsonld.tsx
@@ -2,14 +2,10 @@ import { AppCategory, AppData, FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd, slugify } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
// Map internal app categories to schema.org enumerated applicationCategory values
// https://schema.org/applicationCategory
const APPLICATION_CATEGORY_MAP: Record
= {
@@ -43,8 +39,7 @@ export default async function AppsAppJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -53,13 +48,8 @@ export default async function AppsAppJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -83,8 +73,8 @@ export default async function AppsAppJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
mainEntity: { "@id": `${url}#applications` },
},
{
diff --git a/app/[locale]/apps/[application]/page.tsx b/app/[locale]/apps/[application]/page.tsx
index dbdb19a37b5..dea091fb442 100644
--- a/app/[locale]/apps/[application]/page.tsx
+++ b/app/[locale]/apps/[application]/page.tsx
@@ -43,6 +43,8 @@ import {
import { slugify } from "@/lib/utils/url"
import { formatStringList } from "@/lib/utils/wallets"
+import { DEFAULT_LOCALE } from "@/lib/constants"
+
import ScreenshotSwiper from "./_components/ScreenshotSwiper"
import AppsAppJsonLD from "./page-jsonld"
@@ -148,13 +150,13 @@ const Page = async (props: {
Ethereum.org
-
+
/
ALL APPS
-
+
/
@@ -187,13 +189,13 @@ const Page = async (props: {
chains={app.networks as ChainName[]}
className="mt-2"
/>
-
+
by {app.parentCompany}
-
+
{formatStringList(
formatLanguageNames(app.languages),
5
@@ -273,7 +275,7 @@ const Page = async (props: {
)}
{nextApp && (
-
+
{t("page-apps-see-next")}
@@ -291,7 +293,7 @@ const Page = async (props: {
/>
-
+
)}
@@ -300,9 +302,9 @@ const Page = async (props: {
{nextApp && (
-
+
-
+
{t("page-apps-see-next")}
@@ -318,14 +320,14 @@ const Page = async (props: {
/>
-
+
)}
-
+
{getLocalizedDescription(
appDescriptions,
@@ -334,22 +336,22 @@ const Page = async (props: {
app.description
)}
-
+
{t("page-apps-info-title")}
-
+
{t("page-apps-info-founded")}
{getDisplayYear(app.dateOfLaunch)}
-
+
{t("page-apps-info-creator")}
{app.parentCompany}
-
+
{t("page-apps-info-last-updated")}
{getTimeAgo(app.lastUpdated)}
@@ -368,7 +370,7 @@ const Page = async (props: {
{relatedApps.length > 0 && (
-
+
{t("page-apps-more-apps-like-this")}
{relatedApps.map((relatedApp) => (
@@ -422,38 +424,51 @@ export async function generateMetadata(props: {
const params = await props.params
const { locale, application } = params
- // Fetch apps data using the new data-layer function (already cached)
- const appsData = await getAppsData()
+ try {
+ // Fetch apps data using the new data-layer function (already cached)
+ const appsData = await getAppsData()
- // Handle null case - throw error if required data is missing
- if (!appsData) {
- throw new Error("Failed to fetch apps data")
- }
+ // Handle null case - throw error if required data is missing
+ if (!appsData) {
+ throw new Error("Failed to fetch apps data")
+ }
- const app = Object.values(appsData)
- .flat()
- .find((app) => slugify(app.name) === application)!
+ const app = Object.values(appsData)
+ .flat()
+ .find((app) => slugify(app.name) === application)
- if (!app) {
- notFound()
- }
+ if (!app) {
+ throw new Error(`App not found: ${application}`)
+ }
- const appDescriptions = await getTranslations("page-app-descriptions")
+ const appDescriptions = await getTranslations("page-app-descriptions")
- const title = `Ethereum Apps - ${app.name}` // TODO (i18n): Extract "Ethereum Apps" to namespace
- const description = getLocalizedDescription(
- appDescriptions,
- "app",
- app.name,
- app.description
- )
+ const title = `Ethereum Apps - ${app.name}` // TODO (i18n): Extract "Ethereum Apps" to namespace
+ const description = getLocalizedDescription(
+ appDescriptions,
+ "app",
+ app.name,
+ app.description
+ )
- return await getMetadata({
- locale,
- slug: ["apps", application],
- title,
- description,
- })
+ return await getMetadata({
+ locale,
+ slug: ["apps", application],
+ title,
+ description,
+ })
+ } catch (error) {
+ const t = await getTranslations({
+ locale: DEFAULT_LOCALE,
+ namespace: "common",
+ })
+
+ // Return basic metadata for invalid paths
+ return {
+ title: t("page-not-found"),
+ description: t("page-not-found-description"),
+ }
+ }
}
export default Page
diff --git a/app/[locale]/apps/_components/AppsHighlight.tsx b/app/[locale]/apps/_components/AppsHighlight.tsx
index 40487d68c0a..86d95d366c0 100644
--- a/app/[locale]/apps/_components/AppsHighlight.tsx
+++ b/app/[locale]/apps/_components/AppsHighlight.tsx
@@ -19,7 +19,7 @@ const AppsHighlight = ({ apps, matomoCategory }: AppsHighlightProps) => {
const cards = apps.slice(0, 3).map((app, index) => (
(
-
+
{
const t = await getTranslations("page-apps")
return (
-
+
{t("page-apps-suggest-an-app-title")}
{t("page-apps-suggest-an-app-description")}
{
{Object.keys(appsData).map((category) => (
-
+
{
)
})()}
-
+
{t(appsCategories[category].name)}
diff --git a/app/[locale]/apps/categories/[catetgoryName]/page-jsonld.tsx b/app/[locale]/apps/categories/[catetgoryName]/page-jsonld.tsx
index c2585aa8cd6..4c51a07988f 100644
--- a/app/[locale]/apps/categories/[catetgoryName]/page-jsonld.tsx
+++ b/app/[locale]/apps/categories/[catetgoryName]/page-jsonld.tsx
@@ -4,14 +4,10 @@ import { AppCategoryData, AppData, FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function AppsCategoryJsonLD({
locale,
categoryName,
@@ -40,8 +36,7 @@ export default async function AppsCategoryJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -50,13 +45,8 @@ export default async function AppsCategoryJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -80,8 +70,8 @@ export default async function AppsCategoryJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
mainEntity: { "@id": `${url}#categories` },
},
{
diff --git a/app/[locale]/apps/categories/[catetgoryName]/page.tsx b/app/[locale]/apps/categories/[catetgoryName]/page.tsx
index b3f72ad03b8..d45c67eae6f 100644
--- a/app/[locale]/apps/categories/[catetgoryName]/page.tsx
+++ b/app/[locale]/apps/categories/[catetgoryName]/page.tsx
@@ -149,7 +149,7 @@ const Page = async (props: {
Ethereum.org
-
+
/
@@ -157,7 +157,7 @@ const Page = async (props: {
{t("page-apps-all-apps")}
-
+
/
@@ -223,35 +223,45 @@ export async function generateMetadata(props: {
}) {
const params = await props.params
const { locale, catetgoryName } = params
- const t = await getTranslations("page-apps")
- // Normalize slug to lowercase
- const normalizedSlug = catetgoryName.toLowerCase()
+ try {
+ const t = await getTranslations("page-apps")
- // Find category by matching the slug
- const categoryEntry = Object.entries(appsCategories).find(
- ([, categoryData]) => categoryData.slug === normalizedSlug
- )
+ // Normalize slug to lowercase
+ const normalizedSlug = catetgoryName.toLowerCase()
- if (!categoryEntry) {
- notFound()
- }
+ // Find category by matching the slug
+ const categoryEntry = Object.entries(appsCategories).find(
+ ([, categoryData]) => categoryData.slug === normalizedSlug
+ )
- const [categoryEnum, category] = categoryEntry
+ if (!categoryEntry) {
+ throw new Error(`App category not found: ${catetgoryName}`)
+ }
- if (!isValidCategory(categoryEnum)) {
- notFound()
- }
+ const [categoryEnum, category] = categoryEntry
- const title = t(category.metaTitle)
- const description = t(category.metaDescription)
+ if (!isValidCategory(categoryEnum)) {
+ throw new Error(`Invalid app category enum: ${categoryEnum}`)
+ }
- return await getMetadata({
- locale,
- slug: ["apps", "categories", normalizedSlug],
- title,
- description,
- })
+ const title = t(category.metaTitle)
+ const description = t(category.metaDescription)
+
+ return await getMetadata({
+ locale,
+ slug: ["apps", "categories", normalizedSlug],
+ title,
+ description,
+ })
+ } catch {
+ const t = await getTranslations("common")
+
+ return {
+ title: t("page-not-found"),
+ description: t("page-not-found-description"),
+ }
+ }
}
export default Page
diff --git a/app/[locale]/apps/page-jsonld.tsx b/app/[locale]/apps/page-jsonld.tsx
index 3056fc5acaf..dffac2f2a38 100644
--- a/app/[locale]/apps/page-jsonld.tsx
+++ b/app/[locale]/apps/page-jsonld.tsx
@@ -4,16 +4,12 @@ import { FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
import { appsCategories } from "@/data/apps/categories"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function AppsJsonLD({
locale,
contributors,
@@ -34,8 +30,7 @@ export default async function AppsJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -44,13 +39,8 @@ export default async function AppsJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -68,8 +58,8 @@ export default async function AppsJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
mainEntity: { "@id": `${url}#apps` },
},
{
diff --git a/app/[locale]/assets/_components/assets.tsx b/app/[locale]/assets/_components/assets.tsx
index 208e12ae169..5be7788fc5c 100644
--- a/app/[locale]/assets/_components/assets.tsx
+++ b/app/[locale]/assets/_components/assets.tsx
@@ -76,15 +76,15 @@ import wallet from "@/public/images/wallet.png"
import whatIsEthereum from "@/public/images/what-is-ethereum.png"
const Row = (props: ChildOnlyProp) => (
-
+
)
const H2 = (props: HTMLAttributes) => (
-
+
)
const H3 = (props: ChildOnlyProp) => (
-
+
)
const AssetsPage = () => {
diff --git a/app/[locale]/assets/page-jsonld.tsx b/app/[locale]/assets/page-jsonld.tsx
index 3034ec452c5..0012c2321e8 100644
--- a/app/[locale]/assets/page-jsonld.tsx
+++ b/app/[locale]/assets/page-jsonld.tsx
@@ -4,14 +4,10 @@ import { FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function AssetsJsonLD({
locale,
contributors,
@@ -32,8 +28,7 @@ export default async function AssetsJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -42,13 +37,8 @@ export default async function AssetsJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -66,8 +56,8 @@ export default async function AssetsJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
mainEntity: { "@id": `${url}#assets` },
},
{
@@ -101,8 +91,7 @@ export default async function AssetsJsonLD({
url: normalizeUrlForJsonLd(locale, "/assets/#brand"),
},
],
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
},
],
}
diff --git a/app/[locale]/bug-bounty/page-jsonld.tsx b/app/[locale]/bug-bounty/page-jsonld.tsx
index 8d1feb7b91a..99e30b86438 100644
--- a/app/[locale]/bug-bounty/page-jsonld.tsx
+++ b/app/[locale]/bug-bounty/page-jsonld.tsx
@@ -4,14 +4,12 @@ import { FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+import { KNOWN_PERSONS } from "@/lib/jsonld/persons"
+import { personReference } from "@/lib/jsonld/utils"
+
export default async function BugBountyJsonLD({
locale,
contributors,
@@ -32,8 +30,8 @@ export default async function BugBountyJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ KNOWN_PERSONS["fredrik-svantes"],
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -42,13 +40,11 @@ export default async function BugBountyJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [
+ personReference("fredrik-svantes"),
+ REFERENCE.ETHEREUM_COMMUNITY,
+ ],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -66,8 +62,8 @@ export default async function BugBountyJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
},
],
}
diff --git a/app/[locale]/bug-bounty/page.tsx b/app/[locale]/bug-bounty/page.tsx
index 1ecbef7ad02..237d9c4c18d 100644
--- a/app/[locale]/bug-bounty/page.tsx
+++ b/app/[locale]/bug-bounty/page.tsx
@@ -51,11 +51,11 @@ const Content = (props: ChildOnlyProp) => (
)
const H2 = ({ className, ...props }: ComponentProps<"h2">) => (
-
+
)
const H4 = (props: ChildOnlyProp) => (
-
+
)
const Text = ({ className, ...props }: ComponentProps<"p">) => (
@@ -71,7 +71,7 @@ const ClientRow = (props: ChildOnlyProp) => (
)
const Client = (props: ChildOnlyProp) => (
-
+
)
const Row = (props: ChildOnlyProp) => (
@@ -256,16 +256,16 @@ export default async function Page(props: { params: Promise }) {
{/* */}
-
+
-
{" "}
-
+
{" "}
+
{t("page-upgrades-bug-bounty-title")}
}) {
-
+
{t("page-upgrades-bug-bounty-subtitle")}
@@ -288,7 +288,7 @@ export default async function Page(props: { params: Promise }) {
-
+
{t("page-upgrades-bug-bounty-leaderboard")}
@@ -380,7 +380,7 @@ export default async function Page(props: { params: Promise }) {
/>
-
+
{t("page-upgrades-bug-bounty-validity")}
@@ -394,7 +394,7 @@ export default async function Page(props: { params: Promise
}) {
})}
-
+
}) {
{/* Out of Scope */}
{t("page-upgrades-bug-bounty-not-included")}
-
+
{t.rich("page-upgrades-bug-bounty-not-included-desc", {
a: (chunks) => {chunks},
})}
@@ -564,7 +564,7 @@ export default async function Page(props: { params: Promise }) {
] as const
).map(({ key, footnote }) => (
- ✕
+ ✕
{t(key)}
{footnote && * }
@@ -572,16 +572,16 @@ export default async function Page(props: { params: Promise }) {
))}
-
+
*
{t("page-upgrades-bug-bounty-out-of-scope-footnote")}
{/* Bug Hunting Rules */}
-
+
{t("page-upgrades-bug-bounty-hunting")}
-
+
{t("page-upgrades-bug-bounty-hunting-desc")}
@@ -595,9 +595,9 @@ export default async function Page(props: { params: Promise }) {
).map((key, idx) => (
-
+
{idx + 1}
{t(key)}
@@ -612,12 +612,12 @@ export default async function Page(props: { params: Promise }) {
{t("page-upgrades-bug-bounty-severity-qualifications-title")}
-
+
{t("page-upgrades-bug-bounty-severity-qualifications-desc")}
{/* Low */}
-
+
{t("page-upgrades-bug-bounty-severity-low-title")}
@@ -640,7 +640,7 @@ export default async function Page(props: { params: Promise
}) {
{/* Medium */}
-
+
{t("page-upgrades-bug-bounty-severity-medium-title")}
@@ -663,7 +663,7 @@ export default async function Page(props: { params: Promise
}) {
{/* High */}
-
+
{t("page-upgrades-bug-bounty-severity-high-title")}
@@ -686,7 +686,7 @@ export default async function Page(props: { params: Promise
}) {
{/* Critical */}
-
+
{t("page-upgrades-bug-bounty-severity-critical-title")}
@@ -737,7 +737,7 @@ export default async function Page(props: { params: Promise
}) {
diff --git a/app/[locale]/collectibles/_components/CollectiblesContent/index.tsx b/app/[locale]/collectibles/_components/CollectiblesContent/index.tsx
index 6cf2fea3595..5c6d6f10469 100644
--- a/app/[locale]/collectibles/_components/CollectiblesContent/index.tsx
+++ b/app/[locale]/collectibles/_components/CollectiblesContent/index.tsx
@@ -83,7 +83,7 @@ const CollectiblesContent = ({ badges }: CollectiblesPageProps) => {
return (
{/* Already a contributor? section */}
-
+
) => (
(
-
+
{children}
@@ -112,7 +112,7 @@ const HighlightCardFooter = ({
const CheckList = ({ className, ...props }: ListProps) => (
(
{children}
@@ -253,7 +253,7 @@ const CollectiblesCurrentYear = ({
{t("page-collectibles-instructions-label")}
-
+
-
+
-
+
-
+
{socialBadges.map((badge) => (
-
+
{grouped[year].map((badge: Badge) => {
const sanitizedName = badge.name
.replace(/\s?ethereum.org\s?/i, " ") // Remove "ethereum.org" from label
@@ -97,7 +97,7 @@ const CollectiblesPreviousYears = ({
alt={badge.name}
className="size-16 transition-transform group-hover:scale-105 group-hover:transition-transform md:size-20"
/>
-
+
{label}
diff --git a/app/[locale]/collectibles/_components/CollectiblesProgress/index.tsx b/app/[locale]/collectibles/_components/CollectiblesProgress/index.tsx
index 9ae2fcd1416..1d17732e3f5 100644
--- a/app/[locale]/collectibles/_components/CollectiblesProgress/index.tsx
+++ b/app/[locale]/collectibles/_components/CollectiblesProgress/index.tsx
@@ -61,7 +61,7 @@ const CollectiblesProgress = ({ badges }: CollectiblesProgressProps) => {
value={(ownedCount / (currentYearBadges.length || 1)) * 100}
/>
-
+
{t("page-collectibles-index-frequency")}
>
diff --git a/app/[locale]/collectibles/page-jsonld.tsx b/app/[locale]/collectibles/page-jsonld.tsx
index 83f9c7ed943..60354eb9c9e 100644
--- a/app/[locale]/collectibles/page-jsonld.tsx
+++ b/app/[locale]/collectibles/page-jsonld.tsx
@@ -4,17 +4,13 @@ import { FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
import { COLLECTIBLES_BASE_URL } from "./constants"
import type { Badge, Stats } from "./types"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function CollectiblesJsonLD({
locale,
badges,
@@ -39,8 +35,7 @@ export default async function CollectiblesJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -49,13 +44,8 @@ export default async function CollectiblesJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -73,8 +63,8 @@ export default async function CollectiblesJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
mainEntity: { "@id": `${url}#collectibles` },
},
{
@@ -92,8 +82,7 @@ export default async function CollectiblesJsonLD({
url: badge.link || `${COLLECTIBLES_BASE_URL}/badge/${badge.id}`,
image: badge.image,
})),
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
additionalProperty: [
{
"@type": "PropertyValue",
diff --git a/app/[locale]/collectibles/page.tsx b/app/[locale]/collectibles/page.tsx
index c2d55b8f117..93e711cc0ea 100644
--- a/app/[locale]/collectibles/page.tsx
+++ b/app/[locale]/collectibles/page.tsx
@@ -83,7 +83,7 @@ export default async function Page(props: { params: Promise
}) {
id="stats"
className="flex flex-col gap-x-6 gap-y-4 px-4 xl:flex-row xl:px-12"
>
-
+
{t("page-collectibles-improve-title")}
@@ -110,7 +110,7 @@ export default async function Page(props: { params: Promise
}) {
{/* Minted */}
-
+
{stats.collectorsCount
? numberFormat(locale).format(stats.collectorsCount)
@@ -121,7 +121,7 @@ export default async function Page(props: { params: Promise
}) {
{/* Collectors */}
-
+
{stats.uniqueAddressesCount
? numberFormat(locale).format(stats.uniqueAddressesCount)
@@ -132,7 +132,7 @@ export default async function Page(props: { params: Promise
}) {
{/* Unique Badges */}
-
+
{stats.collectiblesCount
? numberFormat(locale).format(stats.collectiblesCount)
diff --git a/app/[locale]/community/_components/community.tsx b/app/[locale]/community/_components/community.tsx
index 68ce6d2c402..f9a6fda8220 100644
--- a/app/[locale]/community/_components/community.tsx
+++ b/app/[locale]/community/_components/community.tsx
@@ -75,7 +75,7 @@ const ImageContainer = ({ children }: ChildOnlyProp) => {
}
const Subtitle = ({ children }: ChildOnlyProp) => {
- return
{children}
+ return
{children}
}
const FeatureContent = ({ children }: ChildOnlyProp) => {
@@ -92,7 +92,7 @@ const H2 = ({
...props
}: BaseHTMLAttributes
) => {
return (
-
+
{children}
)
@@ -161,7 +161,7 @@ const CommunityPage = () => {
-
+
@@ -171,7 +171,7 @@ const CommunityPage = () => {
{whyGetInvolvedCards.map((card, idx) => (
{
-
+
-
+
{t("page-community-get-involved-title")}
@@ -203,7 +203,7 @@ const CommunityPage = () => {
{cards.map((card, idx) => (
{
-
+
{t("page-community-open-source")}
@@ -243,7 +243,7 @@ const CommunityPage = () => {
-
+
@@ -272,7 +272,7 @@ const CommunityPage = () => {
-
+
{t("page-community-support")}
@@ -295,7 +295,7 @@ const CommunityPage = () => {
-
+
{t("page-community-try-ethereum")}
diff --git a/app/[locale]/community/events/_components/ContinentTabs.tsx b/app/[locale]/community/events/_components/ContinentTabs.tsx
index a63b383b471..dc3d549d85b 100644
--- a/app/[locale]/community/events/_components/ContinentTabs.tsx
+++ b/app/[locale]/community/events/_components/ContinentTabs.tsx
@@ -127,7 +127,7 @@ export default function ContinentTabs({
/>
{filteredEvents.length === 0 ? (
- {noEventsMessage}
+ {noEventsMessage}
) : (
{displayedEvents.map((event) => {
@@ -149,7 +149,7 @@ export default function ContinentTabs({
{/* Logo + Title + Location */}
-
+
-
+
{event.title}
diff --git a/app/[locale]/community/events/_components/EventCard.stories.tsx b/app/[locale]/community/events/_components/EventCard.stories.tsx
new file mode 100644
index 00000000000..b8cb910e1f7
--- /dev/null
+++ b/app/[locale]/community/events/_components/EventCard.stories.tsx
@@ -0,0 +1,101 @@
+import type { Meta, StoryObj } from "@storybook/nextjs"
+
+import type { EventItem } from "@/lib/types"
+
+import { VStack } from "@/components/ui/flex"
+
+import EventCard from "./EventCard"
+
+const meta = {
+ title: "Molecules / Community / EventCard",
+ component: EventCard,
+ decorators: [
+ (Story) => (
+
+
+
+ ),
+ ],
+} satisfies Meta
+
+export default meta
+
+type Story = StoryObj
+
+const baseEvent: EventItem = {
+ id: "devconnect-buenos-aires",
+ title: "Devconnect Buenos Aires",
+ logoImage: "https://avatars.githubusercontent.com/u/6250754?v=4",
+ bannerImage: "https://avatars.githubusercontent.com/u/6250754?v=4",
+ startTime: "2026-11-17T09:00:00Z",
+ endTime: "2026-11-22T18:00:00Z",
+ location: "Buenos Aires, Argentina",
+ link: "#",
+ tags: ["conference"],
+ eventTypes: ["conference"],
+ eventTypesLabels: ["Conference"],
+ isOnline: false,
+ continent: "south-america",
+ discord: null,
+ telegram: null,
+ twitter: null,
+ farcaster: null,
+}
+
+const brokenEvent: EventItem = {
+ ...baseEvent,
+ id: "broken-image-event",
+ logoImage: "https://placehold.co/404error",
+ bannerImage: "https://placehold.co/404error",
+}
+
+export const Grid: Story = {
+ args: {
+ event: baseEvent,
+ variant: "grid",
+ showTypeTag: true,
+ locale: "en",
+ },
+}
+
+export const GridBrokenImageFallback: Story = {
+ args: {
+ event: brokenEvent,
+ variant: "grid",
+ showTypeTag: true,
+ locale: "en",
+ },
+}
+
+export const Highlight: Story = {
+ args: {
+ event: baseEvent,
+ variant: "highlight",
+ locale: "en",
+ },
+}
+
+export const HighlightBrokenImageFallback: Story = {
+ args: {
+ event: brokenEvent,
+ variant: "highlight",
+ locale: "en",
+ },
+}
+
+export const FallbackComparison: Story = {
+ args: {
+ event: brokenEvent,
+ variant: "grid",
+ showTypeTag: true,
+ locale: "en",
+ },
+ render: () => (
+
+
+
+
+
+
+ ),
+}
diff --git a/app/[locale]/community/events/_components/EventCard.tsx b/app/[locale]/community/events/_components/EventCard.tsx
index e0d945b0f7a..a11b3ee0013 100644
--- a/app/[locale]/community/events/_components/EventCard.tsx
+++ b/app/[locale]/community/events/_components/EventCard.tsx
@@ -1,3 +1,6 @@
+"use client"
+
+import { useState } from "react"
import { MapPin } from "lucide-react"
import type { EventItem, MatomoEventOptions } from "@/lib/types"
@@ -15,7 +18,7 @@ interface EventCardProps {
event: EventItem
variant?: "grid" | "highlight"
className?: string
- locale?: string
+ locale: string
showTypeTag?: boolean
customEventOptions?: MatomoEventOptions
}
@@ -26,6 +29,8 @@ function EventCardGrid({
locale,
customEventOptions,
}: EventCardProps) {
+ const [logoError, setLogoError] = useState(false)
+
const primaryType = event.eventTypes?.[0]
const hasDate = Boolean(event.startTime)
@@ -36,7 +41,7 @@ function EventCardGrid({
: null
return (
-
+
-
- {event.logoImage ? (
+
+ {event.logoImage && !logoError ? (
setLogoError(true)}
/>
) : (
-
+
)}
@@ -68,11 +74,11 @@ function EventCardGrid({
{event.eventTypesLabels?.[0] || primaryType}
)}
-
+
{event.title}
{formattedDate &&
{formattedDate}
}
-
{event.location}
+
{event.location}
@@ -85,36 +91,53 @@ function EventCardHighlight({
locale,
customEventOptions,
}: EventCardProps) {
+ const [logoError, setLogoError] = useState(false)
+ const [bannerError, setBannerError] = useState(false)
+
+ const bannerSrc = event.bannerImage || event.logoImage
+
return (
-
+
-
-
-
-
-
+
+ {bannerSrc && !bannerError ? (
setBannerError(true)}
/>
+ ) : (
+
+
+
+ )}
+
+
+
+ {event.logoImage && !logoError ? (
+ setLogoError(true)}
+ />
+ ) : (
+
+ )}
{event.title}
-
{event.location}
-
+
{event.location}
+
{formatDateRange(event.startTime, event.endTime, locale)}
@@ -128,7 +151,7 @@ export default function EventCard({
event,
variant,
className,
- locale = "en",
+ locale,
showTypeTag,
customEventOptions,
}: EventCardProps) {
diff --git a/app/[locale]/community/events/_components/FilterEvents.tsx b/app/[locale]/community/events/_components/FilterEvents.tsx
index b83a0b8d5a8..fbcc1ce5ab5 100644
--- a/app/[locale]/community/events/_components/FilterEvents.tsx
+++ b/app/[locale]/community/events/_components/FilterEvents.tsx
@@ -67,7 +67,7 @@ export default function FilterEvents({ events }: FilterProps) {
return (
<>
-
+
{filteredEvents.slice(0, MAX_RESULTS).map((event) => (
({
+ "@type": "Person",
+ name: contributor.login,
+ url: contributor.html_url,
+ }))
+
+ const datedConferences = conferences.filter((c) => c.startTime)
+
+ const jsonLd = {
+ "@context": "https://schema.org",
+ "@graph": [
+ ...BASE_GRAPH_NODES,
+ {
+ "@type": "CollectionPage",
+ "@id": url,
+ name: t("page-events-conferences-hero-title", { year }),
+ description: t("page-events-meta-description", { year }),
+ url: url,
+ inLanguage: locale,
+ contributor: contributorList,
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
+ breadcrumb: {
+ "@type": "BreadcrumbList",
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Home",
+ item: normalizeUrlForJsonLd(locale, "/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: "Community",
+ item: normalizeUrlForJsonLd(locale, "/community/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ name: common("events"),
+ item: normalizeUrlForJsonLd(locale, "/community/events/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 4,
+ name: t("page-events-section-upcoming-conferences"),
+ item: url,
+ },
+ ],
+ },
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
+ mainEntity: { "@id": `${url}#conferences` },
+ },
+ {
+ "@type": "ItemList",
+ "@id": `${url}#conferences`,
+ name: t("page-events-section-upcoming-conferences"),
+ numberOfItems: datedConferences.length,
+ itemListElement: datedConferences.map((event, i) => ({
+ "@type": "ListItem",
+ position: i + 1,
+ item: toEventNode(event),
+ })),
+ },
+ ],
+ }
+
+ return
+}
diff --git a/app/[locale]/community/events/conferences/page.tsx b/app/[locale]/community/events/conferences/page.tsx
index 3ec263e3f29..34ccb5498ae 100644
--- a/app/[locale]/community/events/conferences/page.tsx
+++ b/app/[locale]/community/events/conferences/page.tsx
@@ -1,7 +1,7 @@
import { pick } from "lodash"
import { getMessages, getTranslations } from "next-intl/server"
-import type { PageParams } from "@/lib/types"
+import type { Lang, PageParams } from "@/lib/types"
import ContentHero from "@/components/Hero/ContentHero"
import I18nProvider from "@/components/I18nProvider"
@@ -10,8 +10,10 @@ import {
EdgeScrollContainer,
EdgeScrollItem,
} from "@/components/ui/edge-scroll-container"
+import Link from "@/components/ui/Link"
import { Section } from "@/components/ui/section"
+import { getAppPageContributorInfo } from "@/lib/utils/contributors"
import { getLocaleYear } from "@/lib/utils/date"
import { getMetadata } from "@/lib/utils/metadata"
import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
@@ -21,6 +23,8 @@ import EventCard from "../_components/EventCard"
import OrganizerCTA from "../_components/OrganizerCTA"
import { mapEventTranslations } from "../utils"
+import ConferencesJsonLD from "./page-jsonld"
+
import { getEventsData } from "@/lib/data"
const Page = async (props: { params: Promise }) => {
@@ -32,7 +36,7 @@ const Page = async (props: { params: Promise }) => {
const t = await getTranslations("page-community-events")
// Apply translations and compute eventTypes from tags if missing
- const events = mapEventTranslations(_events, t)
+ const events = mapEventTranslations(_events, t, locale)
// Filter to conferences only (includes hackathons as they're often conference-adjacent)
const conferences = events.filter(
@@ -52,6 +56,11 @@ const Page = async (props: { params: Promise }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/community/events")
const messages = pick(allMessages, requiredNamespaces)
+ const { contributors } = await getAppPageContributorInfo(
+ "community/events/conferences",
+ locale as Lang
+ )
+
// Continent labels for tabs
const continentLabels = {
all: t("page-events-filter-all"),
@@ -66,6 +75,11 @@ const Page = async (props: { params: Promise }) => {
return (
+
}) => {
eventCategory: "Events_conferences",
}}
/>
+
+ {t.rich("page-events-data-source-callout", {
+ a: (chunks) => {chunks},
+ })}
+
{/* Footer CTA */}
diff --git a/app/[locale]/community/events/meetups/_components/FilterMeetups.tsx b/app/[locale]/community/events/meetups/_components/FilterMeetups.tsx
index 54ac000bfc9..b2c8e200c10 100644
--- a/app/[locale]/community/events/meetups/_components/FilterMeetups.tsx
+++ b/app/[locale]/community/events/meetups/_components/FilterMeetups.tsx
@@ -49,7 +49,7 @@ export default function FilterMeetups({ events }: FilterMeetupsProps) {
{t("page-events-search-sr-text")}
{filteredEvents.length ? (
-
+
{filteredEvents.map((event) => (
({
+ "@type": "Person",
+ name: contributor.login,
+ url: contributor.html_url,
+ }))
+
+ // Only dated events belong in the Event ItemList; ongoing meetup groups
+ // (no startTime) are rendered on the page but aren't schema.org Events.
+ const datedMeetups = meetups.filter((m) => m.startTime)
+
+ const jsonLd = {
+ "@context": "https://schema.org",
+ "@graph": [
+ ...BASE_GRAPH_NODES,
+ {
+ "@type": "CollectionPage",
+ "@id": url,
+ name: t("page-events-meetups-hero-title", { year }),
+ description: t("page-events-meta-description", { year }),
+ url: url,
+ inLanguage: locale,
+ contributor: contributorList,
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
+ breadcrumb: {
+ "@type": "BreadcrumbList",
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Home",
+ item: normalizeUrlForJsonLd(locale, "/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: "Community",
+ item: normalizeUrlForJsonLd(locale, "/community/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ name: common("events"),
+ item: normalizeUrlForJsonLd(locale, "/community/events/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 4,
+ name: t("page-events-section-local-meetups"),
+ item: url,
+ },
+ ],
+ },
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
+ mainEntity: { "@id": `${url}#meetups` },
+ },
+ {
+ "@type": "ItemList",
+ "@id": `${url}#meetups`,
+ name: t("page-events-section-local-meetups"),
+ numberOfItems: datedMeetups.length,
+ itemListElement: datedMeetups.map((event, i) => ({
+ "@type": "ListItem",
+ position: i + 1,
+ item: toEventNode(event),
+ })),
+ },
+ ],
+ }
+
+ return
+}
diff --git a/app/[locale]/community/events/meetups/page.tsx b/app/[locale]/community/events/meetups/page.tsx
index b00fde43761..ce085ef9d57 100644
--- a/app/[locale]/community/events/meetups/page.tsx
+++ b/app/[locale]/community/events/meetups/page.tsx
@@ -1,13 +1,14 @@
import { pick } from "lodash"
import { getMessages, getTranslations } from "next-intl/server"
-import type { PageParams } from "@/lib/types"
+import type { Lang, PageParams } from "@/lib/types"
import ContentHero from "@/components/Hero/ContentHero"
import I18nProvider from "@/components/I18nProvider"
import MainArticle from "@/components/MainArticle"
import { Section } from "@/components/ui/section"
+import { getAppPageContributorInfo } from "@/lib/utils/contributors"
import { getLocaleYear } from "@/lib/utils/date"
import { getMetadata } from "@/lib/utils/metadata"
import { getRequiredNamespacesForPage } from "@/lib/utils/translations"
@@ -16,6 +17,7 @@ import OrganizerCTA from "../_components/OrganizerCTA"
import { getMeetupGroups, mapEventTranslations } from "../utils"
import FilterMeetups from "./_components/FilterMeetups"
+import MeetupsJsonLD from "./page-jsonld"
import { getEventsData } from "@/lib/data"
@@ -28,7 +30,7 @@ const Page = async (props: { params: Promise }) => {
const t = await getTranslations("page-community-events")
// Apply translations and compute eventTypes from tags if missing
- const events = mapEventTranslations(_events, t)
+ const events = mapEventTranslations(_events, t, locale)
// Combine API meetup events with legacy meetup groups
// Exclude conferences and hackathons - they have their own section
@@ -37,7 +39,7 @@ const Page = async (props: { params: Promise }) => {
!e.eventTypes?.includes("conference") &&
!e.eventTypes?.includes("hackathon")
)
- const meetupGroups = getMeetupGroups()
+ const meetupGroups = getMeetupGroups(locale)
// Show API meetups first (sorted by date), then groups (sorted alphabetically)
const meetups = [...apiMeetups, ...meetupGroups]
@@ -45,8 +47,18 @@ const Page = async (props: { params: Promise }) => {
const requiredNamespaces = getRequiredNamespacesForPage("/community/events")
const messages = pick(allMessages, requiredNamespaces)
+ const { contributors } = await getAppPageContributorInfo(
+ "community/events/meetups",
+ locale as Lang
+ )
+
return (
<>
+
}) => {
locale as Lang
)
- const events = mapEventTranslations(_events, t)
+ const events = mapEventTranslations(_events, t, locale)
// Get highlighted conferences (with highlight flag or first 3)
const conferences = events.filter(
@@ -82,7 +82,7 @@ const Page = async (props: { params: Promise }) => {
!e.eventTypes?.includes("conference") &&
!e.eventTypes?.includes("hackathon")
)
- const meetupGroups = getMeetupGroups()
+ const meetupGroups = getMeetupGroups(locale)
const meetups = [...apiMeetups, ...meetupGroups]
// Continent labels for tabs
@@ -202,7 +202,7 @@ const Page = async (props: { params: Promise }) => {
key={id}
className={cn(
"ms-6 w-[calc(100%-4rem)] max-w-96 md:w-96 lg:max-w-[30%] xl:max-w-[22%]",
- "flex flex-col justify-between gap-4 rounded-4xl border p-8 shadow-lg",
+ "rounded-4xl flex flex-col justify-between gap-4 border p-8 shadow-lg",
logoBgColor
)}
>
@@ -259,14 +259,14 @@ const Page = async (props: { params: Promise }) => {
-
+
{t("page-events-hub-apply-cta")}
@@ -277,7 +277,7 @@ const Page = async (props: { params: Promise
}) => {
{/* Find events near you */}
@@ -364,6 +364,13 @@ const Page = async (props: { params: Promise
}) => {
eventName: "regular_conf",
}}
/>
+
+ {t.rich("page-events-data-source-callout", {
+ a: (chunks) => (
+ {chunks}
+ ),
+ })}
+
}) => {
{/* Ethereum Everywhere Card */}
-
+
@@ -492,7 +499,7 @@ const Page = async (props: { params: Promise
}) => {
{/* Geode Labs Card */}
-
+
diff --git a/app/[locale]/community/events/search/page-jsonld.tsx b/app/[locale]/community/events/search/page-jsonld.tsx
new file mode 100644
index 00000000000..ee85535a610
--- /dev/null
+++ b/app/[locale]/community/events/search/page-jsonld.tsx
@@ -0,0 +1,87 @@
+import { getTranslations } from "next-intl/server"
+
+import { FileContributor } from "@/lib/types"
+
+import PageJsonLD from "@/components/PageJsonLD"
+
+import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
+export default async function EventsSearchJsonLD({
+ locale,
+ contributors,
+}: {
+ locale: string
+ contributors: FileContributor[]
+}) {
+ const t = await getTranslations("page-community-events")
+ const common = await getTranslations("common")
+
+ const url = normalizeUrlForJsonLd(locale, `/community/events/search/`)
+
+ const contributorList = contributors.map((contributor) => ({
+ "@type": "Person",
+ name: contributor.login,
+ url: contributor.html_url,
+ }))
+
+ const jsonLd = {
+ "@context": "https://schema.org",
+ "@graph": [
+ ...BASE_GRAPH_NODES,
+ {
+ "@type": "WebPage",
+ "@id": url,
+ name: t("page-events-search-hero-title"),
+ description: t("page-events-search-metadata-description"),
+ url: url,
+ inLanguage: locale,
+ contributor: contributorList,
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
+ breadcrumb: {
+ "@type": "BreadcrumbList",
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Home",
+ item: normalizeUrlForJsonLd(locale, "/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: "Community",
+ item: normalizeUrlForJsonLd(locale, "/community/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ name: common("events"),
+ item: normalizeUrlForJsonLd(locale, "/community/events/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 4,
+ name: common("search"),
+ item: url,
+ },
+ ],
+ },
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
+ potentialAction: {
+ "@type": "SearchAction",
+ target: {
+ "@type": "EntryPoint",
+ urlTemplate: `${url}?q={search_term_string}`,
+ },
+ "query-input": "required name=search_term_string",
+ },
+ },
+ ],
+ }
+
+ return
+}
diff --git a/app/[locale]/community/events/search/page.tsx b/app/[locale]/community/events/search/page.tsx
index 9aff9dd4761..fc314ad60e3 100644
--- a/app/[locale]/community/events/search/page.tsx
+++ b/app/[locale]/community/events/search/page.tsx
@@ -1,7 +1,7 @@
import { Info } from "lucide-react"
import { getTranslations, setRequestLocale } from "next-intl/server"
-import type { EventItem, PageParams } from "@/lib/types"
+import type { EventItem, Lang, PageParams } from "@/lib/types"
import ContentHero from "@/components/Hero/ContentHero"
import MainArticle from "@/components/MainArticle"
@@ -10,12 +10,15 @@ import { Button } from "@/components/ui/buttons/Button"
import Input from "@/components/ui/input"
import { Section } from "@/components/ui/section"
+import { getAppPageContributorInfo } from "@/lib/utils/contributors"
import { getMetadata } from "@/lib/utils/metadata"
import EventCard from "../_components/EventCard"
import OrganizerCTA from "../_components/OrganizerCTA"
import { mapEventTranslations, sanitize } from "../utils"
+import EventsSearchJsonLD from "./page-jsonld"
+
import { getEventsData } from "@/lib/data"
const safeDecodeURIComponent = (str: string) => {
@@ -41,7 +44,7 @@ const Page = async (props: {
const t = await getTranslations("page-community-events")
const tCommon = await getTranslations("common")
- const events = mapEventTranslations(_events, t)
+ const events = mapEventTranslations(_events, t, locale)
const filteredEvents = ((): EventItem[] => {
if (!q) return []
@@ -79,7 +82,7 @@ const Page = async (props: {
return (
<>
-
+
{filteredEvents.map((event) => (
+
export const mapEventTranslations = (
events: EventItem[],
- t: ReturnType
+ t: ReturnType,
+ locale: string
): EventItem[] =>
events.map((event) => {
// Use existing eventTypes if they have values, otherwise compute from tags
@@ -38,6 +42,7 @@ export const mapEventTranslations = (
...event,
eventTypes,
eventTypesLabels: eventTypes.map((type) => t(`page-events-tag-${type}`)),
+ location: localizeLocation(event.location, locale),
}
})
@@ -50,14 +55,14 @@ interface MeetupGroup {
bannerImage?: string
}
-function transformMeetupGroup(group: MeetupGroup): EventItem {
+function transformMeetupGroup(group: MeetupGroup, locale: string): EventItem {
return {
title: group.title,
logoImage: group.logoImage || "",
bannerImage: group.bannerImage || "",
startTime: "",
endTime: null,
- location: group.location,
+ location: localizeLocation(group.location, locale),
link: group.link,
tags: ["meetup"],
id: slugify(`${group.title}-${group.location}`),
@@ -71,8 +76,8 @@ function transformMeetupGroup(group: MeetupGroup): EventItem {
* Get meetup groups from community-meetups.json
* These are ongoing community groups (not individual events with dates)
*/
-export function getMeetupGroups(): EventItem[] {
+export function getMeetupGroups(locale: string): EventItem[] {
return (communityMeetups as MeetupGroup[])
- .map(transformMeetupGroup)
+ .map((group) => transformMeetupGroup(group, locale))
.sort((a, b) => a.title.localeCompare(b.title))
}
diff --git a/app/[locale]/community/page-jsonld.tsx b/app/[locale]/community/page-jsonld.tsx
index 1a8b3439d11..63aa81b48f4 100644
--- a/app/[locale]/community/page-jsonld.tsx
+++ b/app/[locale]/community/page-jsonld.tsx
@@ -4,14 +4,10 @@ import { FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function CommunityJsonLD({
locale,
contributors,
@@ -32,8 +28,7 @@ export default async function CommunityJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -42,13 +37,8 @@ export default async function CommunityJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -66,8 +56,8 @@ export default async function CommunityJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
mainEntity: { "@id": `${url}#resources` },
},
{
@@ -107,8 +97,7 @@ export default async function CommunityJsonLD({
url: normalizeUrlForJsonLd(locale, "/community/grants/"),
},
],
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
},
],
}
diff --git a/app/[locale]/community/support/data.tsx b/app/[locale]/community/support/data.tsx
new file mode 100644
index 00000000000..0ea7439709a
--- /dev/null
+++ b/app/[locale]/community/support/data.tsx
@@ -0,0 +1,155 @@
+import { BookOpen, HelpCircle, Shield, ShieldAlert } from "lucide-react"
+
+import type { ItemSection } from "./types"
+
+export const sections: {
+ getHelp: ItemSection[]
+ learn: ItemSection[]
+} = {
+ getHelp: [
+ {
+ titleKey: "page-community-support-something-went-wrong",
+ Svg: ShieldAlert,
+ colorClass: "text-accent-b",
+ descriptionKey: "page-community-support-something-went-wrong-description",
+ eventAction: "Something went wrong",
+ items: [
+ {
+ labelKey: "page-community-support-lost-funds-scam",
+ href: "/community/support/scams/",
+ eventName: "I lost funds to a scam or fraud",
+ },
+ {
+ labelKey: "page-community-support-secure-remaining-funds",
+ href: "/community/support/scams/#secure-assets",
+ eventName: "Secure remaining funds and revoke permissions",
+ },
+ {
+ labelKey: "page-community-support-report-scam",
+ href: "/community/support/scams/#report",
+ eventName: "Report a scam address or website",
+ },
+ {
+ labelKey: "page-community-support-trace-funds",
+ href: "/community/support/scams/#analyze",
+ eventName: "Trace where funds were sent",
+ },
+ {
+ labelKey: "page-community-support-sent-wrong-address",
+ href: "/community/support/faq/#wrong-wallet",
+ eventName: "I sent to the wrong address",
+ },
+ {
+ labelKey: "page-community-support-lost-wallet-access",
+ href: "/community/support/faq/#lost-wallet-access",
+ eventName: "I lost access to my wallet",
+ },
+ {
+ labelKey: "page-community-support-stuck-transaction",
+ href: "/community/support/faq/#stuck-transaction",
+ eventName: "My transaction is stuck",
+ },
+ ],
+ },
+ {
+ titleKey: "page-community-support-protect-yourself",
+ Svg: Shield,
+ colorClass: "text-accent-a",
+ descriptionKey: "page-community-support-protect-yourself-description",
+ eventAction: "Protect yourself",
+ items: [
+ {
+ labelKey: "page-community-support-common-scam-tactics",
+ href: "/security/#common-scams",
+ eventName: "Common scam tactics and how to spot them",
+ },
+ {
+ labelKey: "page-community-support-recovery-experts-scams",
+ href: "/community/support/scams/#recovery-scams",
+ eventName: "Why recovery experts are always scams",
+ },
+ {
+ labelKey: "page-community-support-identify-scam-tokens",
+ href: "/guides/how-to-id-scam-tokens/",
+ eventName: "How to identify scam tokens",
+ },
+ {
+ labelKey: "page-community-support-full-security-guide",
+ href: "/security/",
+ eventName: "Full security and scam prevention guide",
+ },
+ {
+ labelKey: "page-community-support-revoke-approvals",
+ href: "/community/support/scams/#revoke-approvals",
+ eventName: "Revoke unnecessary token approvals",
+ },
+ ],
+ },
+ ],
+ learn: [
+ {
+ titleKey: "page-community-support-using-ethereum",
+ Svg: BookOpen,
+ colorClass: "text-primary",
+ descriptionKey: "page-community-support-using-ethereum-description",
+ eventAction: "Using Ethereum",
+ items: [
+ {
+ labelKey: "page-community-support-create-account",
+ href: "/guides/how-to-create-an-ethereum-account/",
+ eventName: "How to create an Ethereum account",
+ },
+ {
+ labelKey: "page-community-support-use-wallet",
+ href: "/guides/how-to-use-a-wallet/",
+ eventName: "How to use a wallet",
+ },
+ {
+ labelKey: "page-community-support-swap-tokens",
+ href: "/guides/how-to-swap-tokens/",
+ eventName: "How to swap tokens",
+ },
+ {
+ labelKey: "page-community-support-bridge-tokens",
+ href: "/guides/how-to-use-a-bridge/",
+ eventName: "How to bridge tokens to layer 2",
+ },
+ {
+ labelKey: "page-community-support-revoke-token-access",
+ href: "/guides/how-to-revoke-token-access/",
+ eventName: "How to revoke token access",
+ },
+ ],
+ },
+ {
+ titleKey: "page-community-support-common-misconceptions",
+ Svg: HelpCircle,
+ colorClass: "text-accent-c",
+ descriptionKey:
+ "page-community-support-common-misconceptions-description",
+ eventAction: "Common misconceptions",
+ items: [
+ {
+ labelKey: "page-community-support-is-ethereum-company",
+ href: "/community/support/misconceptions/#not-a-company",
+ eventName: "Is Ethereum a company?",
+ },
+ {
+ labelKey: "page-community-support-recover-freeze-funds",
+ href: "/community/support/misconceptions/#no-fund-access",
+ eventName: "Can someone recover or freeze my funds?",
+ },
+ {
+ labelKey: "page-community-support-mine-ethereum",
+ href: "/community/support/misconceptions/#no-mining",
+ eventName: "Can I still mine Ethereum?",
+ },
+ {
+ labelKey: "page-community-support-is-support-team",
+ href: "/community/support/misconceptions/#no-support-team",
+ eventName: "Is there an Ethereum support team?",
+ },
+ ],
+ },
+ ],
+}
diff --git a/app/[locale]/community/support/page-jsonld.tsx b/app/[locale]/community/support/page-jsonld.tsx
new file mode 100644
index 00000000000..6e709eb215b
--- /dev/null
+++ b/app/[locale]/community/support/page-jsonld.tsx
@@ -0,0 +1,105 @@
+import { getTranslations } from "next-intl/server"
+
+import { FileContributor } from "@/lib/types"
+
+import PageJsonLD from "@/components/PageJsonLD"
+
+import { isExternal, normalizeUrlForJsonLd } from "@/lib/utils/url"
+
+import { sections } from "./data"
+
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
+export default async function SupportJsonLD({
+ locale,
+ contributors,
+}: {
+ locale: string
+ contributors: FileContributor[]
+}) {
+ const t = await getTranslations("page-community-support")
+
+ const url = normalizeUrlForJsonLd(locale, `/community/support/`)
+
+ const contributorList = contributors.map((contributor) => ({
+ "@type": "Person",
+ name: contributor.login,
+ url: contributor.html_url,
+ }))
+
+ // Flatten all card items in page render order, then append the Discord CTA.
+ const links = [
+ ...[...sections.getHelp, ...sections.learn].flatMap((section) =>
+ section.items.map(({ labelKey, href }) => ({
+ labelKey,
+ href,
+ }))
+ ),
+ // Page-terminating external CTA ("Still need help?" Discord button).
+ // Not part of the sections data struct, so appended manually.
+ {
+ labelKey: "page-community-support-discord",
+ href: "https://discord.gg/ethereum-org",
+ },
+ ]
+
+ const supportItems = links.map(({ labelKey, href }, i) => ({
+ "@type": "ListItem",
+ position: i + 1,
+ name: t(labelKey as Parameters[0]),
+ url: isExternal(href) ? href : normalizeUrlForJsonLd(locale, href),
+ }))
+
+ const jsonLd = {
+ "@context": "https://schema.org",
+ "@graph": [
+ ...BASE_GRAPH_NODES,
+ {
+ "@type": "WebPage",
+ "@id": url,
+ name: t("page-community-support-hero-title"),
+ description: t("page-community-support-meta-description"),
+ url: url,
+ inLanguage: locale,
+ contributor: contributorList,
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
+ breadcrumb: {
+ "@type": "BreadcrumbList",
+ itemListElement: [
+ {
+ "@type": "ListItem",
+ position: 1,
+ name: "Home",
+ item: normalizeUrlForJsonLd(locale, "/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 2,
+ name: "Community",
+ item: normalizeUrlForJsonLd(locale, "/community/"),
+ },
+ {
+ "@type": "ListItem",
+ position: 3,
+ name: t("page-community-support-hero-title"),
+ item: url,
+ },
+ ],
+ },
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
+ mainEntity: { "@id": `${url}#support-links` },
+ },
+ {
+ "@type": "ItemList",
+ "@id": `${url}#support-links`,
+ name: t("page-community-support-hero-title"),
+ numberOfItems: supportItems.length,
+ itemListElement: supportItems,
+ },
+ ],
+ }
+
+ return
+}
diff --git a/app/[locale]/community/support/page.tsx b/app/[locale]/community/support/page.tsx
index 6a0c32c8cb7..d2a05075eaa 100644
--- a/app/[locale]/community/support/page.tsx
+++ b/app/[locale]/community/support/page.tsx
@@ -1,7 +1,7 @@
-import { BookOpen, HelpCircle, Shield, ShieldAlert } from "lucide-react"
+import { Shield } from "lucide-react"
import { getTranslations, setRequestLocale } from "next-intl/server"
-import type { PageParams } from "@/lib/types"
+import type { Lang, PageParams } from "@/lib/types"
import Breadcrumbs from "@/components/Breadcrumbs"
import FeedbackCard from "@/components/FeedbackCard"
@@ -19,8 +19,13 @@ import Link from "@/components/ui/Link"
import { Section } from "@/components/ui/section"
import WindowBox from "@/components/WindowBox"
+import { cn } from "@/lib/utils/cn"
+import { getAppPageContributorInfo } from "@/lib/utils/contributors"
import { getMetadata } from "@/lib/utils/metadata"
+import { sections } from "./data"
+import SupportJsonLD from "./page-jsonld"
+
const EVENT_CATEGORY = "Support"
export default async function Page(props: { params: Promise }) {
@@ -31,14 +36,20 @@ export default async function Page(props: { params: Promise }) {
const t = await getTranslations("page-community-support")
+ const { contributors } = await getAppPageContributorInfo(
+ "community/support",
+ locale as Lang
+ )
+
return (
+
{/* Hero */}
}
title={t("page-community-support-hero-title")}
subtitle={
-
+
{t("page-community-support-hero-subtitle-1")}
@@ -53,7 +64,7 @@ export default async function Page(props: { params: Promise
}) {
{/* Decentralization alert */}
-
+
@@ -73,165 +84,43 @@ export default async function Page(props: { params: Promise }) {
{t("page-community-support-get-help")}
- {/* Card 1: Something went wrong */}
-
{t("page-community-support-something-went-wrong")}
- }
- svg={ }
- className="h-fit"
- >
-
-
- {t("page-community-support-something-went-wrong-description")}
-
-
- {t("page-community-support-lost-funds-scam")}
-
-
- {t("page-community-support-secure-remaining-funds")}
-
-
- {t("page-community-support-report-scam")}
-
-
- {t("page-community-support-trace-funds")}
-
-
- {t("page-community-support-sent-wrong-address")}
-
-
- {t("page-community-support-lost-wallet-access")}
-
-
- {t("page-community-support-stuck-transaction")}
-
-
-
-
- {/* Card 2: Protect yourself */}
-
{t("page-community-support-protect-yourself")}}
- svg={ }
- className="h-fit"
- >
-
-
- {t("page-community-support-protect-yourself-description")}
-
-
- {t("page-community-support-common-scam-tactics")}
-
-
- {t("page-community-support-recovery-experts-scams")}
-
-
- {t("page-community-support-identify-scam-tokens")}
-
-
- {t("page-community-support-full-security-guide")}
-
-
- {t("page-community-support-revoke-approvals")}
-
-
-
+ {sections.getHelp.map(
+ ({
+ titleKey,
+ Svg,
+ colorClass,
+ descriptionKey,
+ eventAction,
+ items,
+ }) => (
+
{t(titleKey)}}
+ svg={ }
+ className="h-fit"
+ >
+
+
+ {t(descriptionKey)}
+
+ {items.map(({ labelKey, href, eventName }) => (
+
+ {t(labelKey)}
+
+ ))}
+
+
+ )
+ )}
@@ -241,147 +130,56 @@ export default async function Page(props: { params: Promise }) {
{t("page-community-support-learn")}
- {/* Card 3: Using Ethereum */}
-
{t("page-community-support-using-ethereum")}}
- svg={ }
- className="h-fit"
- >
-
-
- {t("page-community-support-using-ethereum-description")}
-
-
- {t("page-community-support-create-account")}
-
-
- {t("page-community-support-use-wallet")}
-
-
- {t("page-community-support-swap-tokens")}
-
-
- {t("page-community-support-bridge-tokens")}
-
-
- {t("page-community-support-revoke-token-access")}
-
-
-
-
- {/* Card 4: Common misconceptions */}
-
{t("page-community-support-common-misconceptions")}
- }
- svg={ }
- className="h-fit"
- >
-
-
- {t(
- "page-community-support-common-misconceptions-description"
- )}
-
-
- {t("page-community-support-is-ethereum-company")}
-
-
- {t("page-community-support-recover-freeze-funds")}
-
-
- {t("page-community-support-mine-ethereum")}
-
-
- {t("page-community-support-is-support-team")}
-
-
-
+ {sections.learn.map(
+ ({
+ titleKey,
+ Svg,
+ colorClass,
+ descriptionKey,
+ eventAction,
+ items,
+ }) => (
+
{t(titleKey)}}
+ svg={ }
+ className="h-fit"
+ >
+
+
+ {t(descriptionKey)}
+
+ {items.map(({ labelKey, href, eventName }) => (
+
+ {t(labelKey)}
+
+ ))}
+
+
+ )
+ )}
{/* Still need help? */}
{t("page-community-support-still-need-help")}
-
+
{t("page-community-support-still-need-help-description")}
& RefAttributes
+ >
+ colorClass: string
+ descriptionKey: string
+ eventAction: string
+ items: { labelKey: string; href: string; eventName: string }[]
+}
diff --git a/app/[locale]/contributing/translation-program/acknowledgements/_components/acknowledgements.tsx b/app/[locale]/contributing/translation-program/acknowledgements/_components/acknowledgements.tsx
index 5fe387a4371..15f8be562db 100644
--- a/app/[locale]/contributing/translation-program/acknowledgements/_components/acknowledgements.tsx
+++ b/app/[locale]/contributing/translation-program/acknowledgements/_components/acknowledgements.tsx
@@ -37,7 +37,7 @@ const H2 = ({
className,
...props
}: BaseHTMLAttributes) => (
-
+
)
const Text = ({
@@ -62,14 +62,14 @@ const TranslatorAcknowledgements = () => {
-
+
{t(
"page-contributing-translation-program-acknowledgements-acknowledgement-page-title"
)}
-
+
{/* LEFT COLUMN */}
-
+
{t(
"page-contributing-translation-program-acknowledgements-acknowledgement-page-1"
@@ -102,7 +102,7 @@ const TranslatorAcknowledgements = () => {
*/}
{/* RIGHT COLUMN */}
-
+
{
{t("page-contributing-translation-program-acknowledgements-3")}
-
+
{t(
"page-contributing-translation-program-acknowledgements-how-to-claim-title"
)}
diff --git a/app/[locale]/contributing/translation-program/acknowledgements/page-jsonld.tsx b/app/[locale]/contributing/translation-program/acknowledgements/page-jsonld.tsx
index 39859ad4e5d..00b324e8455 100644
--- a/app/[locale]/contributing/translation-program/acknowledgements/page-jsonld.tsx
+++ b/app/[locale]/contributing/translation-program/acknowledgements/page-jsonld.tsx
@@ -4,14 +4,10 @@ import { FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function AcknowledgementsJsonLD({
locale,
contributors,
@@ -37,8 +33,7 @@ export default async function AcknowledgementsJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -51,13 +46,8 @@ export default async function AcknowledgementsJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -92,8 +82,8 @@ export default async function AcknowledgementsJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
},
],
}
diff --git a/app/[locale]/contributing/translation-program/contributors/_components/contributors.tsx b/app/[locale]/contributing/translation-program/contributors/_components/contributors.tsx
index 1d379bf2c9d..4c15ed0df24 100644
--- a/app/[locale]/contributing/translation-program/contributors/_components/contributors.tsx
+++ b/app/[locale]/contributing/translation-program/contributors/_components/contributors.tsx
@@ -43,10 +43,10 @@ const Contributors = () => {
-
+
{t("page-contributing-translation-program-contributors-title")}
-
+
{t(
"page-contributing-translation-program-contributors-number-of-contributors"
)}{" "}
@@ -74,7 +74,7 @@ const Contributors = () => {
.
-
+
{t("page-contributing-translation-program-contributors-thank-you")}
diff --git a/app/[locale]/contributing/translation-program/contributors/page-jsonld.tsx b/app/[locale]/contributing/translation-program/contributors/page-jsonld.tsx
index bbc9f2236d8..6fe48b3b382 100644
--- a/app/[locale]/contributing/translation-program/contributors/page-jsonld.tsx
+++ b/app/[locale]/contributing/translation-program/contributors/page-jsonld.tsx
@@ -4,14 +4,10 @@ import { FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function ContributorsJsonLD({
locale,
contributors,
@@ -37,8 +33,7 @@ export default async function ContributorsJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -51,13 +46,8 @@ export default async function ContributorsJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -92,8 +82,8 @@ export default async function ContributorsJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
},
],
}
diff --git a/app/[locale]/contributing/translation-program/translatathon/leaderboard/_components/Leaderboard.tsx b/app/[locale]/contributing/translation-program/translatathon/leaderboard/_components/Leaderboard.tsx
index 21cf5f2188a..2dfd4992561 100644
--- a/app/[locale]/contributing/translation-program/translatathon/leaderboard/_components/Leaderboard.tsx
+++ b/app/[locale]/contributing/translation-program/translatathon/leaderboard/_components/Leaderboard.tsx
@@ -57,7 +57,7 @@ export const Leaderboard = () => {
}
return (
-
+
#
@@ -83,7 +83,7 @@ export const Leaderboard = () => {
return (
@@ -112,7 +112,7 @@ export const Leaderboard = () => {
onClick={showMore}
className="m-2 mx-0 flex h-full w-full items-center justify-center rounded-full px-6 py-4 lg:mx-2 lg:w-auto"
>
-
+
Show more
diff --git a/app/[locale]/contributing/translation-program/translatathon/leaderboard/page-jsonld.tsx b/app/[locale]/contributing/translation-program/translatathon/leaderboard/page-jsonld.tsx
index 789cde9736f..6afbee664fa 100644
--- a/app/[locale]/contributing/translation-program/translatathon/leaderboard/page-jsonld.tsx
+++ b/app/[locale]/contributing/translation-program/translatathon/leaderboard/page-jsonld.tsx
@@ -2,14 +2,10 @@ import { FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function TranslatathonLeaderboardJsonLD({
locale,
contributors,
@@ -31,8 +27,7 @@ export default async function TranslatathonLeaderboardJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -42,13 +37,8 @@ export default async function TranslatathonLeaderboardJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -90,8 +80,8 @@ export default async function TranslatathonLeaderboardJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
},
],
}
diff --git a/app/[locale]/developers/_components/BuilderCard.tsx b/app/[locale]/developers/_components/BuilderCard.tsx
index 40bb6b87616..5573c135b65 100644
--- a/app/[locale]/developers/_components/BuilderCard.tsx
+++ b/app/[locale]/developers/_components/BuilderCard.tsx
@@ -25,13 +25,13 @@ const BuilderCard = ({ path, className }: BuildCardProps) => (
{path.tag}
)}
{path.title}
- {path.description}
+ {path.description}
(
-
+
-
-
+
+
)
diff --git a/app/[locale]/developers/_components/SpeedRunCard.tsx b/app/[locale]/developers/_components/SpeedRunCard.tsx
index 9218419cadb..2e72fdd4a67 100644
--- a/app/[locale]/developers/_components/SpeedRunCard.tsx
+++ b/app/[locale]/developers/_components/SpeedRunCard.tsx
@@ -27,12 +27,12 @@ const SpeedRunCard = ({
alt="SpeedRunEthereum banner"
sizes="(max-width: 768px) 100vw, 50vw"
/>
-
+
{title}
{description}
(
{course.hours}
-
+
{course.title}
- {course.description}
+ {course.description}
)
diff --git a/app/[locale]/developers/page-jsonld.tsx b/app/[locale]/developers/page-jsonld.tsx
index 8d064c29c22..bff93c429aa 100644
--- a/app/[locale]/developers/page-jsonld.tsx
+++ b/app/[locale]/developers/page-jsonld.tsx
@@ -4,16 +4,12 @@ import { EventItem, FileContributor } from "@/lib/types"
import PageJsonLD from "@/components/PageJsonLD"
-import {
- ethereumCommunityOrganization,
- ethereumCommunityReference,
- ethereumFoundationOrganization,
- ethereumFoundationReference,
-} from "@/lib/utils/jsonld"
import { normalizeUrlForJsonLd } from "@/lib/utils/url"
import { DevelopersPath, VideoCourse } from "./types"
+import { BASE_GRAPH_NODES, REFERENCE } from "@/lib/jsonld/constants"
+
export default async function DevelopersPageJsonLD({
locale,
paths,
@@ -40,8 +36,7 @@ export default async function DevelopersPageJsonLD({
const jsonLd = {
"@context": "https://schema.org",
"@graph": [
- ethereumFoundationOrganization,
- ethereumCommunityOrganization,
+ ...BASE_GRAPH_NODES,
{
"@type": "WebPage",
"@id": url,
@@ -50,13 +45,8 @@ export default async function DevelopersPageJsonLD({
url: url,
inLanguage: locale,
contributor: contributorList,
- author: [ethereumCommunityReference],
- isPartOf: {
- "@type": "WebSite",
- "@id": "https://ethereum.org/#website",
- name: "ethereum.org",
- url: "https://ethereum.org",
- },
+ author: [REFERENCE.ETHEREUM_COMMUNITY],
+ isPartOf: REFERENCE.ETHEREUM_ORG_WEBSITE,
breadcrumb: {
"@type": "BreadcrumbList",
itemListElement: [
@@ -74,8 +64,8 @@ export default async function DevelopersPageJsonLD({
},
],
},
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
+ reviewedBy: REFERENCE.ETHEREUM_FOUNDATION,
mainEntity: { "@id": `${url}#developers` },
},
{
@@ -108,8 +98,7 @@ export default async function DevelopersPageJsonLD({
url: hackathon.link,
})),
],
- publisher: ethereumFoundationReference,
- reviewedBy: ethereumFoundationReference,
+ publisher: REFERENCE.ETHEREUM_FOUNDATION,
},
],
}
diff --git a/app/[locale]/developers/page.tsx b/app/[locale]/developers/page.tsx
index e0174d0f026..968a7acf2c5 100644
--- a/app/[locale]/developers/page.tsx
+++ b/app/[locale]/developers/page.tsx
@@ -48,17 +48,17 @@ import scaffoldDebugScreenshot from "@/public/images/developers/scaffold-debug-s
import stackExchangeScreenshot from "@/public/images/developers/stack-exchange-screenshot.png"
import tutorialTagsBanner from "@/public/images/developers/tutorial-tags-banner.png"
import dogeImage from "@/public/images/doge-computer.png"
-import EventFallback from "@/public/images/events/event-placeholder.png"
+import fallbackThumbnail from "@/public/images/eth-glyph-thumbnail.png"
import heroImage from "@/public/images/heroes/developers-hub-hero.png"
-const H3 = (props: ChildOnlyProp) =>
+const H3 = (props: ChildOnlyProp) =>
const Text = (props: ChildOnlyProp) =>
const Column = (props: ChildOnlyProp) => (
-
+
)
const RightColumn = (props: ChildOnlyProp) => (
-
+
)
const Scroller = ({
@@ -103,9 +103,9 @@ const WhyGrid = () => {
return (
{items.map(({ heading, description }) => (
@@ -186,7 +186,7 @@ const DevelopersPage = async (props: { params: Promise
}) => {
id="why"
className={cn(
"grid grid-cols-1 gap-6 md:gap-10 lg:grid-cols-2",
- "-mx-8 w-screen max-w-screen-2xl items-center bg-background-highlight px-8 py-10 md:py-20"
+ "bg-background-highlight -mx-8 w-screen max-w-screen-2xl items-center px-8 py-10 md:py-20"
)}
>
@@ -261,7 +261,7 @@ const DevelopersPage = async (props: { params: Promise
}) => {
id="resources"
className={cn(
"grid grid-cols-1 gap-6 md:grid-cols-2 md:gap-8",
- "-mx-8 w-screen max-w-screen-2xl bg-background-highlight px-8 py-10 md:py-20"
+ "bg-background-highlight -mx-8 w-screen max-w-screen-2xl px-8 py-10 md:py-20"
)}
>
@@ -269,7 +269,7 @@ const DevelopersPage = async (props: { params: Promise }) => {
{/* Quickstart your idea */}
-
+
}) => {
/>
{t("page-developers-jump-right-in-title")}
-
+
{t("page-developers-quickstart-scaffold-subtext")}{" "}
}) => {
-
+
npx create-eth@latest
@@ -324,7 +324,7 @@ const DevelopersPage = async (props: { params: Promise
}) => {
{/* Get help */}
-
+
}) => {
/>
{t("page-developers-get-help-title")}
-
+
{t("page-developers-get-help-desc")}
@@ -362,7 +362,7 @@ const DevelopersPage = async (props: { params: Promise }) => {
{/* Resources */}
-
+
}) => {
/>
{t("page-developers-resources-title")}
-
+
{t("page-developers-resources-desc")}
@@ -393,7 +393,7 @@ const DevelopersPage = async (props: { params: Promise }) => {
{/* Tutorials */}
-
+
}) => {
/>
{t("page-developers-tutorials-title")}
-
+
{t("page-developers-tutorials-desc")}
@@ -449,7 +449,7 @@ const DevelopersPage = async (props: { params: Promise }) => {
id="docs"
className={cn(
"shadow-table-item-box",
- "-mx-8 w-screen max-w-screen-2xl bg-background-highlight px-8 py-10 md:py-20"
+ "bg-background-highlight -mx-8 w-screen max-w-screen-2xl px-8 py-10 md:py-20"
)}
>
@@ -598,13 +598,13 @@ const DevelopersPage = async (props: { params: Promise
}) => {
eventAction: "click",
eventName: title,
}}
- className="min-w-72 max-w-md flex-1"
+ className="max-w-md min-w-72 flex-1"
>
{bannerImage ? (
) : (
-
+
)}
@@ -642,12 +642,12 @@ const DevelopersPage = async (props: { params: Promise }) => {