diff --git a/.all-contributorsrc b/.all-contributorsrc index ab5dccaa3e9..6fb97babcd6 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -12725,6 +12725,13 @@ "bug" ] }, + { + "login": "hotequil", + "name": "João Paulo Hotequil", + "avatar_url": "https://avatars.githubusercontent.com/u/46814712?v=4", + "profile": "https://github.com/hotequil", + "contributions": ["code", "translation"] + }, { "login": "microHoffman", "name": "microHoffman", @@ -12733,6 +12740,15 @@ "contributions": [ "bug" ] + }, + { + "login": "smithrashell", + "name": "Rashell Smith", + "avatar_url": "https://avatars.githubusercontent.com/u/36465023?v=4", + "profile": "https://github.com/smithrashell", + "contributions": [ + "bug" + ] } ], "contributorsPerLine": 7, diff --git a/README.md b/README.md index f1472e9b75a..c5bd91a858a 100644 --- a/README.md +++ b/README.md @@ -1040,7 +1040,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d yash
yash

🤔 Isaac Beale
Isaac Beale

📖 🐛 Bal Krishna Jha
Bal Krishna Jha

📖 - mradziwon
mradziwon

💻 🐛 + maciejrrr
maciejrrr

💻 🐛 mmilenkovic
mmilenkovic

📖 🤔 Fernando Guevara
Fernando Guevara

📖 Jose Manuel Garcia Rivas
Jose Manuel Garcia Rivas

🤔 @@ -1948,6 +1948,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d JJOptimist
JJOptimist

🐛 microHoffman
microHoffman

🐛 + Rashell Smith
Rashell Smith

🐛 @@ -1962,3 +1963,5 @@ This project follows the [all-contributors](https://github.com/all-contributors/ ### Join our Discord server We have a space to discuss all things ethereum.org – share your ideas or just say hi over [on Discord](https://discord.gg/ethereum-org). + +[🔼Back to top!](#Table-of-contents) diff --git a/app/[locale]/[...slug]/page.tsx b/app/[locale]/[...slug]/page.tsx index 28020fde0ff..8f7229b3039 100644 --- a/app/[locale]/[...slug]/page.tsx +++ b/app/[locale]/[...slug]/page.tsx @@ -7,6 +7,7 @@ import mdComponents from "@/components/MdComponents" import { dataLoader } from "@/lib/utils/data/dataLoader" import { dateToString } from "@/lib/utils/date" +import { getLayoutFromSlug } from "@/lib/utils/layout" import { getPostSlugs } from "@/lib/utils/md" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -19,18 +20,6 @@ import { getMdMetadata } from "@/lib/md/metadata" const loadData = dataLoader([["gfissues", fetchGFIs]]) -function getLayoutFromSlug(slug: string) { - if (slug.includes("developers/docs")) { - return "docs" - } - - if (slug.includes("developers/tutorials")) { - return "tutorial" - } - - return "static" -} - export default async function Page({ params, }: { @@ -69,7 +58,8 @@ export default async function Page({ slug, // TODO: Address component typing error here (flip `FC` types to prop object types) // @ts-expect-error Incompatible component function signatures - components: { ...mdComponents, ...componentsMapping }, + baseComponents: mdComponents, + componentsMapping, scope: { gfissues, }, diff --git a/app/[locale]/_components/home.tsx b/app/[locale]/_components/home.tsx index 3eab8b4151f..35e96f6b69e 100644 --- a/app/[locale]/_components/home.tsx +++ b/app/[locale]/_components/home.tsx @@ -651,6 +651,8 @@ const HomePage = ({ e.currentTarget.onerror = null e.currentTarget.src = EventFallback.src }} + referrerPolicy="no-referrer" + crossOrigin="anonymous" /> @@ -738,6 +740,8 @@ const HomePage = ({ }} className="max-w-full object-cover object-center" loading="lazy" + referrerPolicy="no-referrer" + crossOrigin="anonymous" /> ) : ( diff --git a/app/[locale]/bug-bounty/_components/bug-bounty.tsx b/app/[locale]/bug-bounty/_components/bug-bounty.tsx index 6a6813a92b5..c7711b64673 100644 --- a/app/[locale]/bug-bounty/_components/bug-bounty.tsx +++ b/app/[locale]/bug-bounty/_components/bug-bounty.tsx @@ -2,7 +2,7 @@ import { HTMLAttributes } from "react" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" /* Uncomment for Bug Bounty Banner: */ import BugBountyBanner from "@/components/Banners/BugBountyBanner" @@ -13,6 +13,7 @@ import CardList from "@/components/CardList" import Emoji from "@/components/Emoji" import ExpandableCard from "@/components/ExpandableCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import { Image, type ImageProps } from "@/components/Image" import Leaderboard from "@/components/Leaderboard" import MainArticle from "@/components/MainArticle" @@ -224,7 +225,10 @@ const sortBountyHuntersFn = (a: BountyHuntersArg, b: BountyHuntersArg) => { return b.score - a.score } -const BugBountiesPage = () => { +const BugBountiesPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const pathname = usePathname() const { t } = useTranslation("page-bug-bounty") @@ -768,6 +772,11 @@ const BugBountiesPage = () => { + diff --git a/app/[locale]/bug-bounty/page.tsx b/app/[locale]/bug-bounty/page.tsx index 5c7bc2a5cdc..4d27d1ac6b1 100644 --- a/app/[locale]/bug-bounty/page.tsx +++ b/app/[locale]/bug-bounty/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { type Params } from "@/lib/types" +import type { CommitHistory, Lang, Params } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ export default async function Page({ params }: { params: Promise }) { const requiredNamespaces = getRequiredNamespacesForPage("/bug-bounty") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "bug-bounty", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/dapps/_components/dapps.tsx b/app/[locale]/dapps/_components/dapps.tsx index a514bd5aa66..f5915b3f7c0 100644 --- a/app/[locale]/dapps/_components/dapps.tsx +++ b/app/[locale]/dapps/_components/dapps.tsx @@ -11,7 +11,7 @@ import React, { import { useSearchParams } from "next/navigation" import { useLocale } from "next-intl" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import BoxGrid from "@/components/BoxGrid" import Callout from "@/components/Callout" @@ -20,6 +20,7 @@ import Card from "@/components/Card" import DocLink from "@/components/DocLink" import Emoji from "@/components/Emoji" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import GhostCard from "@/components/GhostCard" import { Image } from "@/components/Image" import InfoBanner from "@/components/InfoBanner" @@ -270,7 +271,10 @@ interface Categories { [key: string]: Category } -const DappsPage = () => { +const DappsPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation(["page-dapps", "common"]) const searchParams = useSearchParams() const locale = useLocale() @@ -538,7 +542,7 @@ const DappsPage = () => { { title: "Curve", description: t("page-dapps-dapp-description-curve"), - link: "https://curve.fi/", + link: "https://www.curve.finance/", image: curve, alt: t("page-dapps-curve-logo-alt"), }, @@ -1107,7 +1111,7 @@ const DappsPage = () => {

{t("page-dapps-explore-dapps-title")}

{t("page-dapps-explore-dapps-description")}

{t("page-dapps-choose-category")}

-
+
{categoryKeys.map((key, idx) => { const categoryType = key as CategoryType const category = categories[categoryType] @@ -1530,6 +1534,11 @@ const DappsPage = () => { + diff --git a/app/[locale]/dapps/page.tsx b/app/[locale]/dapps/page.tsx index 422fb87b0b9..db19d93875a 100644 --- a/app/[locale]/dapps/page.tsx +++ b/app/[locale]/dapps/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Params } from "@/lib/types" +import type { CommitHistory, Lang, Params } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -22,9 +23,16 @@ export default async function Page({ params }: { params: Promise }) { const requiredNamespaces = getRequiredNamespacesForPage("/dapps") const pickedMessages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo("dapps", locale as Lang, commitHistoryCache) + return ( - + ) } diff --git a/app/[locale]/developers/page.tsx b/app/[locale]/developers/page.tsx index ac02d8b6ade..b1d2b3c9288 100644 --- a/app/[locale]/developers/page.tsx +++ b/app/[locale]/developers/page.tsx @@ -38,13 +38,16 @@ export async function generateMetadata({ }) { const { locale } = await params - const t = await getTranslations({ locale, namespace: "page-developers" }) + const t = await getTranslations({ + locale, + namespace: "page-developers-index", + }) return await getMetadata({ locale, slug: ["developers"], - title: t("page-developers-index:page-developer-meta-title"), - description: t("page-developers-index:page-developers-meta-desc"), + title: t("page-developer-meta-title"), + description: t("page-developers-meta-desc"), }) } diff --git a/app/[locale]/developers/tutorials/_components/tutorials.tsx b/app/[locale]/developers/tutorials/_components/tutorials.tsx index cb0649dc061..879f4ba9cbc 100644 --- a/app/[locale]/developers/tutorials/_components/tutorials.tsx +++ b/app/[locale]/developers/tutorials/_components/tutorials.tsx @@ -34,6 +34,8 @@ import { import externalTutorials from "@/data/externalTutorials.json" +import { DEFAULT_LOCALE } from "@/lib/constants" + import { useBreakpointValue } from "@/hooks/useBreakpointValue" type LinkFlexProps = FlexProps & { @@ -93,14 +95,15 @@ const TutorialPage = ({ contentNotTranslated, }: TutorialPageProps) => { const locale = useLocale() + const effectiveLocale = internalTutorials.length > 0 ? locale : DEFAULT_LOCALE const filteredTutorialsByLang = useMemo( () => filterTutorialsByLang( internalTutorials, externalTutorials, - locale as Lang + effectiveLocale as Lang ), - [internalTutorials, locale] + [internalTutorials, effectiveLocale] ) const allTags = useMemo( diff --git a/app/[locale]/eth/_components/eth.tsx b/app/[locale]/eth/_components/eth.tsx index 8e1990fdeb2..6130958f5c2 100644 --- a/app/[locale]/eth/_components/eth.tsx +++ b/app/[locale]/eth/_components/eth.tsx @@ -2,7 +2,7 @@ import type { ComponentProps, HTMLAttributes } from "react" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import ActionCard from "@/components/ActionCard" import CalloutBanner from "@/components/CalloutBanner" @@ -11,6 +11,7 @@ import CardList from "@/components/CardList" import EthPriceCard from "@/components/EthPriceCard" import EthVideo from "@/components/EthVideo" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import HorizontalCard from "@/components/HorizontalCard" import { Image } from "@/components/Image" import InfoBanner from "@/components/InfoBanner" @@ -168,7 +169,10 @@ const CentralActionCard = (props: ComponentProps) => ( ) -const EthPage = () => { +const EthPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-eth") const pathname = usePathname() @@ -448,6 +452,11 @@ const EthPage = () => { + diff --git a/app/[locale]/eth/page.tsx b/app/[locale]/eth/page.tsx index ede37c897b5..bc1dbe58b60 100644 --- a/app/[locale]/eth/page.tsx +++ b/app/[locale]/eth/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -28,9 +29,16 @@ export default async function Page({ const requiredNamespaces = getRequiredNamespacesForPage("/eth") const pickedMessages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo("eth", locale as Lang, commitHistoryCache) + return ( - + ) } diff --git a/app/[locale]/gas/_components/gas.tsx b/app/[locale]/gas/_components/gas.tsx index 79fdcdd3022..7730c5ae519 100644 --- a/app/[locale]/gas/_components/gas.tsx +++ b/app/[locale]/gas/_components/gas.tsx @@ -2,11 +2,14 @@ import { BaseHTMLAttributes, ComponentPropsWithRef } from "react" +import { PageWithContributorsProps } from "@/lib/types" + import Callout from "@/components/Callout" import Card from "@/components/Card" import Emoji from "@/components/Emoji" import ExpandableCard from "@/components/ExpandableCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import GhostCard from "@/components/GhostCard" import HorizontalCard from "@/components/HorizontalCard" import { Image } from "@/components/Image" @@ -78,7 +81,10 @@ const H3 = ({

) -const GasPage = () => { +const GasPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-gas") const benefits = [ @@ -389,6 +395,11 @@ const GasPage = () => { + diff --git a/app/[locale]/gas/page.tsx b/app/[locale]/gas/page.tsx index da0d304490b..cb4c21c8ef2 100644 --- a/app/[locale]/gas/page.tsx +++ b/app/[locale]/gas/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,16 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/gas") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo("gas", locale as Lang, commitHistoryCache) + return ( - + ) } diff --git a/app/[locale]/get-eth/_components/get-eth.tsx b/app/[locale]/get-eth/_components/get-eth.tsx index 3bc1bc54216..a2a1bfa5472 100644 --- a/app/[locale]/get-eth/_components/get-eth.tsx +++ b/app/[locale]/get-eth/_components/get-eth.tsx @@ -2,7 +2,7 @@ import type { ReactNode } from "react" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import CalloutBanner from "@/components/CalloutBanner" import CardList, { @@ -12,6 +12,7 @@ import CentralizedExchanges from "@/components/CentralizedExchanges" import Emoji from "@/components/Emoji" import EthPriceCard from "@/components/EthPriceCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" import Translation from "@/components/Translation" @@ -63,11 +64,15 @@ const TwoColumnContent = (props: ChildOnlyProp) => (
) -type Props = { +type Props = PageWithContributorsProps & { lastDataUpdateDate: string } -const GetEthPage = ({ lastDataUpdateDate }: Props) => { +const GetEthPage = ({ + lastDataUpdateDate, + contributors, + lastEditLocaleTimestamp, +}: Props) => { const { t } = useTranslation("page-get-eth") const walletImageWidth = useBreakpointValue({ @@ -352,6 +357,12 @@ const GetEthPage = ({ lastDataUpdateDate }: Props) => {
+ + diff --git a/app/[locale]/get-eth/page.tsx b/app/[locale]/get-eth/page.tsx index f41dc4116ea..ffbc9a52eeb 100644 --- a/app/[locale]/get-eth/page.tsx +++ b/app/[locale]/get-eth/page.tsx @@ -5,11 +5,12 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" -import { getLastModifiedDateByPath } from "@/lib/utils/gh" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" +import { getLastGitCommitDateByPath } from "@/lib/utils/gh" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -26,7 +27,7 @@ export default async function Page({ setRequestLocale(locale) - const lastDataUpdateDate = getLastModifiedDateByPath( + const lastDataUpdateDate = getLastGitCommitDateByPath( "src/data/exchangesByCountry.ts" ) @@ -35,9 +36,21 @@ export default async function Page({ const requiredNamespaces = getRequiredNamespacesForPage("/get-eth") const pickedMessages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "get-eth", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/layer-2/learn/_components/learn.tsx b/app/[locale]/layer-2/learn/_components/learn.tsx index 4596b6ab2b2..fca0ae52ce7 100644 --- a/app/[locale]/layer-2/learn/_components/learn.tsx +++ b/app/[locale]/layer-2/learn/_components/learn.tsx @@ -1,7 +1,10 @@ "use client" +import { PageWithContributorsProps } from "@/lib/types" + import Callout from "@/components/Callout" import Card from "@/components/Card" +import FileContributors from "@/components/FileContributors" import { ContentHero, type ContentHeroProps } from "@/components/Hero" import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" @@ -19,7 +22,10 @@ import Callout1Image from "@/public/images/man-and-dog-playing.png" import DAOImage from "@/public/images/use-cases/dao-2.png" import WhatIsEthereumImage from "@/public/images/what-is-ethereum.png" -const Layer2Learn = () => { +const Layer2Learn = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-layer-2-learn") const pathname = usePathname() @@ -274,6 +280,11 @@ const Layer2Learn = () => {

+
diff --git a/app/[locale]/layer-2/learn/page.tsx b/app/[locale]/layer-2/learn/page.tsx index f34506409db..38966be5b6a 100644 --- a/app/[locale]/layer-2/learn/page.tsx +++ b/app/[locale]/layer-2/learn/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/layer-2/learn") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "layer-2/learn", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/learn/_components/learn.tsx b/app/[locale]/learn/_components/learn.tsx index 8b8058afe78..428303a2b12 100644 --- a/app/[locale]/learn/_components/learn.tsx +++ b/app/[locale]/learn/_components/learn.tsx @@ -2,13 +2,18 @@ import type { HTMLAttributes, ReactNode } from "react" -import type { ChildOnlyProp, ToCItem } from "@/lib/types" +import type { + ChildOnlyProp, + PageWithContributorsProps, + ToCItem, +} from "@/lib/types" import OriginalCard, { type CardProps as OriginalCardProps, } from "@/components/Card" import DocLink from "@/components/DocLink" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import { HubHero } from "@/components/Hero" import type { HubHeroProps } from "@/components/Hero/HubHero" import { Image, type ImageProps } from "@/components/Image" @@ -111,7 +116,10 @@ const ImageHeight200 = ({ src, alt }: ImageProps) => ( {alt} ) -const LearnPage = () => { +const LearnPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-learn") const tocItems = [ @@ -681,6 +689,11 @@ const LearnPage = () => { + diff --git a/app/[locale]/learn/page.tsx b/app/[locale]/learn/page.tsx index ac96b2a9303..2499272af27 100644 --- a/app/[locale]/learn/page.tsx +++ b/app/[locale]/learn/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { type Params } from "@/lib/types" +import type { CommitHistory, Lang, Params } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,16 @@ export default async function Page({ params }: { params: Promise }) { const requiredNamespaces = getRequiredNamespacesForPage("/learn") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo("learn", locale as Lang, commitHistoryCache) + return ( - + ) } diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index 9e2713e92b8..ec1256fb079 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -13,6 +13,7 @@ import { dataLoader } from "@/lib/utils/data/dataLoader" import { isValidDate } from "@/lib/utils/date" import { existsNamespace } from "@/lib/utils/existsNamespace" import { getLastDeployDate } from "@/lib/utils/getLastDeployDate" +import { getMetadata } from "@/lib/utils/metadata" import { polishRSSList } from "@/lib/utils/rss" import { getLocaleTimestamp } from "@/lib/utils/time" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -127,13 +128,21 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { ) } -export async function generateMetadata() { - const t = await getTranslations() +export async function generateMetadata({ + params, +}: { + params: Promise<{ locale: string }> +}) { + const { locale } = await params - return { - title: t("page-index.page-index-meta-title"), - description: t("page-index.page-index-meta-description"), - } + const t = await getTranslations({ locale, namespace: "page-index" }) + + return await getMetadata({ + locale, + slug: [""], + title: t("page-index-meta-title"), + description: t("page-index-meta-description"), + }) } export default Page diff --git a/app/[locale]/resources/_components/resources.tsx b/app/[locale]/resources/_components/resources.tsx index d8d2e50476d..11dd09d0c68 100644 --- a/app/[locale]/resources/_components/resources.tsx +++ b/app/[locale]/resources/_components/resources.tsx @@ -125,8 +125,12 @@ const ResourcesPage = ({ txCostsMedianUsd }: ResourcesPageProps) => {
{metric && metric} - {items.map((item) => ( - + {items.map(({ className, ...item }) => ( + ))}
diff --git a/app/[locale]/roadmap/vision/_components/vision.tsx b/app/[locale]/roadmap/vision/_components/vision.tsx index a8bc8a05fd6..8c2a745772f 100644 --- a/app/[locale]/roadmap/vision/_components/vision.tsx +++ b/app/[locale]/roadmap/vision/_components/vision.tsx @@ -2,12 +2,13 @@ import type { ComponentProps, ComponentPropsWithRef } from "react" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import Breadcrumbs from "@/components/Breadcrumbs" import Card from "@/components/Card" import Emoji from "@/components/Emoji" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import InfoBanner from "@/components/InfoBanner" import MainArticle from "@/components/MainArticle" import PageHero, { @@ -90,7 +91,10 @@ const TrilemmaContent = (props: ChildOnlyProp) => ( /> ) -const VisionPage = () => { +const VisionPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation(["page-roadmap-vision", "page-upgrades-index"]) const pathname = usePathname() @@ -240,6 +244,11 @@ const VisionPage = () => { + diff --git a/app/[locale]/roadmap/vision/page.tsx b/app/[locale]/roadmap/vision/page.tsx index a3fdf6e5e95..dececb01be6 100644 --- a/app/[locale]/roadmap/vision/page.tsx +++ b/app/[locale]/roadmap/vision/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/roadmap/vision") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "roadmap/vision", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/run-a-node/_components/run-a-node.tsx b/app/[locale]/run-a-node/_components/run-a-node.tsx index c4f3716ef14..0ed6747c249 100644 --- a/app/[locale]/run-a-node/_components/run-a-node.tsx +++ b/app/[locale]/run-a-node/_components/run-a-node.tsx @@ -4,11 +4,12 @@ import { HTMLAttributes } from "react" import type { ReactNode } from "react" import { FaDiscord } from "react-icons/fa" -import type { ChildOnlyProp } from "@/lib/types" +import type { ChildOnlyProp, PageWithContributorsProps } from "@/lib/types" import Emoji from "@/components/Emoji" import ExpandableCard from "@/components/ExpandableCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import type { IconBaseType } from "@/components/icons/icon-base" import { DecentralizationGlyphIcon, @@ -204,7 +205,10 @@ type RunANodeCard = { alt: string } -const RunANodePage = () => { +const RunANodePage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const { t } = useTranslation("page-run-a-node") const heroContent = { title: t("page-run-a-node-title"), @@ -756,6 +760,11 @@ const RunANodePage = () => { + diff --git a/app/[locale]/run-a-node/page.tsx b/app/[locale]/run-a-node/page.tsx index 65ab9816cda..9e083b5e734 100644 --- a/app/[locale]/run-a-node/page.tsx +++ b/app/[locale]/run-a-node/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/run-a-node") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "run-a-node", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/staking/_components/staking.tsx b/app/[locale]/staking/_components/staking.tsx index d12ace0b8aa..733d00ad66a 100644 --- a/app/[locale]/staking/_components/staking.tsx +++ b/app/[locale]/staking/_components/staking.tsx @@ -2,12 +2,17 @@ import { type HTMLAttributes, ReactNode } from "react" -import type { ChildOnlyProp, StakingStatsData } from "@/lib/types" +import type { + ChildOnlyProp, + PageWithContributorsProps, + StakingStatsData, +} from "@/lib/types" import { List as ButtonDropdownList } from "@/components/ButtonDropdown" import Card from "@/components/Card" import ExpandableCard from "@/components/ExpandableCard" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import LeftNavBar from "@/components/LeftNavBar" import { ContentContainer, Page } from "@/components/MdComponents" import MobileButtonDropdown from "@/components/MobileButtonDropdown" @@ -100,11 +105,15 @@ const StyledCard = (props: { ) -type Props = { +type Props = PageWithContributorsProps & { data: StakingStatsData } -const StakingPage = ({ data }: Props) => { +const StakingPage = ({ + data, + contributors, + lastEditLocaleTimestamp, +}: Props) => { const { t } = useTranslation("page-staking") const heroContent = { @@ -539,6 +548,11 @@ const StakingPage = ({ data }: Props) => { +
diff --git a/app/[locale]/staking/page.tsx b/app/[locale]/staking/page.tsx index 84c7a466c42..37d909903d1 100644 --- a/app/[locale]/staking/page.tsx +++ b/app/[locale]/staking/page.tsx @@ -6,6 +6,7 @@ import { } from "next-intl/server" import { + CommitHistory, EpochResponse, EthStoreResponse, Lang, @@ -14,6 +15,7 @@ import { import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { dataLoader } from "@/lib/utils/data/dataLoader" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -71,9 +73,21 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/staking") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "staking", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/wallets/_components/wallets.tsx b/app/[locale]/wallets/_components/wallets.tsx index 9f633396037..000b5a7c6cc 100644 --- a/app/[locale]/wallets/_components/wallets.tsx +++ b/app/[locale]/wallets/_components/wallets.tsx @@ -3,10 +3,13 @@ import { ComponentPropsWithRef } from "react" import { useLocale } from "next-intl" +import { PageWithContributorsProps } from "@/lib/types" + import Callout from "@/components/Callout" import Card from "@/components/Card" import CardList from "@/components/CardList" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import HorizontalCard from "@/components/HorizontalCard" import { Image } from "@/components/Image" import ListenToPlayer from "@/components/ListenToPlayer" @@ -35,7 +38,10 @@ export const StyledCard = (props: ComponentPropsWithRef) => ( /> ) -const WalletsPage = () => { +const WalletsPage = ({ + contributors, + lastEditLocaleTimestamp, +}: PageWithContributorsProps) => { const pathname = usePathname() const locale = useLocale() const { t } = useTranslation("page-wallets") @@ -418,6 +424,11 @@ const WalletsPage = () => {
+
diff --git a/app/[locale]/wallets/find-wallet/_components/find-wallet.tsx b/app/[locale]/wallets/find-wallet/_components/find-wallet.tsx index acdc94c9aad..5a50080d516 100644 --- a/app/[locale]/wallets/find-wallet/_components/find-wallet.tsx +++ b/app/[locale]/wallets/find-wallet/_components/find-wallet.tsx @@ -2,18 +2,12 @@ import type { ChildOnlyProp, Wallet } from "@/lib/types" -import BannerNotification from "@/components/Banners/BannerNotification" import Breadcrumbs from "@/components/Breadcrumbs" import FindWalletProductTable from "@/components/FindWalletProductTable" -import { Image } from "@/components/Image" import MainArticle from "@/components/MainArticle" -import InlineLink from "@/components/ui/Link" - -import { cn } from "@/lib/utils/cn" import { useTranslation } from "@/hooks/useTranslation" import { usePathname } from "@/i18n/routing" -import HeroImage from "@/public/images/wallets/wallet-hero.png" const Subtitle = ({ children }: ChildOnlyProp) => (

@@ -31,41 +25,12 @@ const FindWalletPage = ({ wallets }: Props) => { return ( - - {t("page-find-wallet-footnote-1")} - - -

-
- -

- {t("page-find-wallet-title")} -

- {t("page-find-wallet-description")} - - {t("page-find-wallet-desc-2")}{" "} - - {t("page-find-wallet-desc-2-wallets-link")} - - -
-
- -
+
+ +

+ {t("page-find-wallet-title")} +

+ {t("page-find-wallet-description")}
diff --git a/app/[locale]/wallets/page.tsx b/app/[locale]/wallets/page.tsx index c83a90cf3bc..b4187b8dc98 100644 --- a/app/[locale]/wallets/page.tsx +++ b/app/[locale]/wallets/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -24,9 +25,20 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/wallets") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "wallets", + locale as Lang, + commitHistoryCache + ) + return ( - + ) } diff --git a/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx b/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx index e50d251ac0c..cf81c8f7ec4 100644 --- a/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx +++ b/app/[locale]/what-is-ethereum/_components/what-is-ethereum.tsx @@ -5,7 +5,12 @@ import { useLocale } from "next-intl" import type { HTMLAttributes } from "react" import { MdInfoOutline } from "react-icons/md" -import type { ChildOnlyProp, Lang, MetricReturnData } from "@/lib/types" +import type { + ChildOnlyProp, + Lang, + MetricReturnData, + PageWithContributorsProps, +} from "@/lib/types" import AdoptionChart from "@/components/AdoptionChart" import { @@ -19,6 +24,7 @@ import Callout from "@/components/Callout" import Card from "@/components/Card" import EnergyConsumptionChart from "@/components/EnergyConsumptionChart" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import { Image } from "@/components/Image" import ListenToPlayer from "@/components/ListenToPlayer" import MainArticle from "@/components/MainArticle" @@ -160,11 +166,15 @@ const Image400 = ({ src }: Pick) => ( ) -type Props = { +type Props = PageWithContributorsProps & { data: MetricReturnData } -const WhatIsEthereumPage = ({ data }: Props) => { +const WhatIsEthereumPage = ({ + data, + contributors, + lastEditLocaleTimestamp, +}: Props) => { const { t } = useTranslation(["page-what-is-ethereum", "learn-quizzes"]) const pathname = usePathname() const locale = useLocale() @@ -835,6 +845,11 @@ const WhatIsEthereumPage = ({ data }: Props) => {
+
diff --git a/app/[locale]/what-is-ethereum/page.tsx b/app/[locale]/what-is-ethereum/page.tsx index 41adad5a2f6..dc9a31746fd 100644 --- a/app/[locale]/what-is-ethereum/page.tsx +++ b/app/[locale]/what-is-ethereum/page.tsx @@ -5,10 +5,11 @@ import { setRequestLocale, } from "next-intl/server" -import { Lang } from "@/lib/types" +import type { CommitHistory, Lang } from "@/lib/types" import I18nProvider from "@/components/I18nProvider" +import { getAppPageContributorInfo } from "@/lib/utils/contributors" import { dataLoader } from "@/lib/utils/data/dataLoader" import { getMetadata } from "@/lib/utils/metadata" import { getRequiredNamespacesForPage } from "@/lib/utils/translations" @@ -29,12 +30,24 @@ const Page = async ({ params }: { params: Promise<{ locale: Lang }> }) => { const requiredNamespaces = getRequiredNamespacesForPage("/what-is-ethereum") const messages = pick(allMessages, requiredNamespaces) + const commitHistoryCache: CommitHistory = {} + const { contributors, lastEditLocaleTimestamp } = + await getAppPageContributorInfo( + "what-is-ethereum", + locale as Lang, + commitHistoryCache + ) + // Load data const [data] = await loadData() return ( - + ) } diff --git a/i18n.config.json b/i18n.config.json index 0edc23ef50e..25708fe362d 100644 --- a/i18n.config.json +++ b/i18n.config.json @@ -5,7 +5,8 @@ "name": "English", "localName": "English", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "am", @@ -13,7 +14,8 @@ "name": "Amharic", "localName": "አማርኛ", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ar", @@ -21,7 +23,8 @@ "name": "Arabic", "localName": "العربية", "langDir": "rtl", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "az", @@ -29,7 +32,8 @@ "name": "Azerbaijani", "localName": "Azərbaycan", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "be", @@ -37,7 +41,8 @@ "name": "Belarusian", "localName": "беларускі", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "bg", @@ -45,7 +50,8 @@ "name": "Bulgarian", "localName": "български", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "bn", @@ -53,7 +59,8 @@ "name": "Bengali", "localName": "বাংলা", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "bs", @@ -61,7 +68,8 @@ "name": "Bosnian", "localName": "босански", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ca", @@ -69,7 +77,8 @@ "name": "Catalan", "localName": "Català", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "cs", @@ -77,7 +86,8 @@ "name": "Czech", "localName": "Čeština", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "da", @@ -85,7 +95,8 @@ "name": "Danish", "localName": "Dansk", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "de", @@ -93,7 +104,8 @@ "name": "German", "localName": "Deutsch", "langDir": "ltr", - "dateFormat": "DD/MM/YYYY" + "dateFormat": "DD/MM/YYYY", + "validISO639_1": true }, { "code": "el", @@ -101,7 +113,8 @@ "name": "Greek", "localName": "Ελληνικά", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "es", @@ -109,7 +122,8 @@ "name": "Spanish", "localName": "Español", "langDir": "ltr", - "dateFormat": "DD/MM/YYYY" + "dateFormat": "DD/MM/YYYY", + "validISO639_1": true }, { "code": "fa", @@ -117,7 +131,8 @@ "name": "Farsi", "localName": "فارسی", "langDir": "rtl", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "fi", @@ -125,7 +140,8 @@ "name": "Finnish", "localName": "Suomi", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "fr", @@ -133,7 +149,8 @@ "name": "French", "localName": "Français", "langDir": "ltr", - "dateFormat": "DD/MM/YYYY" + "dateFormat": "DD/MM/YYYY", + "validISO639_1": true }, { "code": "ga", @@ -141,7 +158,8 @@ "name": "Irish", "localName": "Gaeilge", "langDir": "ltr", - "dateFormat": "DD/MM/YYYY" + "dateFormat": "DD/MM/YYYY", + "validISO639_1": true }, { "code": "gl", @@ -149,7 +167,8 @@ "name": "Galician", "localName": "Galego", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "gu", @@ -157,7 +176,8 @@ "name": "Gujarati", "localName": "ગુજરાતી", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ha", @@ -165,7 +185,8 @@ "name": "Hausa", "localName": "Hausa", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "he", @@ -173,7 +194,8 @@ "name": "Hebrew", "localName": "עִבְרִית", "langDir": "rtl", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "hi", @@ -181,7 +203,8 @@ "name": "Hindi", "localName": "हिन्दी", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "hr", @@ -189,7 +212,8 @@ "name": "Croatian", "localName": "Hrvatski", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "hu", @@ -197,7 +221,8 @@ "name": "Hungarian", "localName": "Magyar", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "hy-am", @@ -205,7 +230,8 @@ "name": "Armenian", "localName": "հայերեն", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "id", @@ -213,7 +239,8 @@ "name": "Indonesian", "localName": "Bahasa Indonesia", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ig", @@ -221,7 +248,8 @@ "name": "Igbo", "localName": "Ibo", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "it", @@ -229,7 +257,8 @@ "name": "Italian", "localName": "Italiano", "langDir": "ltr", - "dateFormat": "DD/MM/YYYY" + "dateFormat": "DD/MM/YYYY", + "validISO639_1": true }, { "code": "ja", @@ -237,7 +266,8 @@ "name": "Japanese", "localName": "日本語", "langDir": "ltr", - "dateFormat": "YYYY/MM/DD" + "dateFormat": "YYYY/MM/DD", + "validISO639_1": true }, { "code": "ka", @@ -245,7 +275,8 @@ "name": "Georgian", "localName": "ქართული", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "kk", @@ -253,7 +284,8 @@ "name": "Kazakh", "localName": "қазақ", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "km", @@ -261,7 +293,8 @@ "name": "Khmer", "localName": "ចក្រភពខ្មែរ", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "kn", @@ -269,7 +302,8 @@ "name": "Kannada", "localName": "ಕನ್ನಡ", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ko", @@ -277,7 +311,8 @@ "name": "Korean", "localName": "한국어", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "lt", @@ -285,7 +320,8 @@ "name": "Lithuanian", "localName": "Lietuvis", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ml", @@ -293,7 +329,8 @@ "name": "Malayalam", "localName": "മലയാളം", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "mr", @@ -301,7 +338,8 @@ "name": "Marathi", "localName": "मराठी", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ms", @@ -309,7 +347,8 @@ "name": "Malay", "localName": "Melayu", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ne-np", @@ -317,7 +356,8 @@ "name": "Nepali", "localName": "नेपाली", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "nl", @@ -325,7 +365,8 @@ "name": "Dutch", "localName": "Nederlands", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "nb", @@ -333,7 +374,8 @@ "name": "Norwegian", "localName": "Norsk", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "pcm", @@ -341,7 +383,8 @@ "name": "Nigerian Pidgin", "localName": "Nigerian Pidgin", "langDir": "ltr", - "dateFormat": "DD/MM/YYYY" + "dateFormat": "DD/MM/YYYY", + "validISO639_1": false }, { "code": "fil", @@ -349,7 +392,8 @@ "name": "Filipino", "localName": "Filipino", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": false }, { "code": "pl", @@ -357,7 +401,8 @@ "name": "Polish", "localName": "Polski", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "pt-br", @@ -365,7 +410,8 @@ "name": "Portuguese (Brazilian)", "localName": "Português", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "pt", @@ -373,7 +419,8 @@ "name": "Portuguese", "localName": "Português", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ro", @@ -381,7 +428,8 @@ "name": "Romanian", "localName": "Română", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ru", @@ -389,7 +437,8 @@ "name": "Russian", "localName": "Pусский", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "se", @@ -397,7 +446,8 @@ "name": "Swedish", "localName": "Svenska", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "sk", @@ -405,7 +455,8 @@ "name": "Slovak", "localName": "Slovenský", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "sl", @@ -413,7 +464,8 @@ "name": "Slovenian", "localName": "Slovenščina", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "sn", @@ -421,7 +473,8 @@ "name": "Shona", "localName": "Shona", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "sr", @@ -429,7 +482,8 @@ "name": "Serbian", "localName": "Српски", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "sw", @@ -437,7 +491,8 @@ "name": "Swahili", "localName": "Kiswahili", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ta", @@ -445,7 +500,8 @@ "name": "Tamil", "localName": "தமிழ்", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "te", @@ -453,7 +509,8 @@ "name": "Telugu", "localName": "తెలుగు", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "tk", @@ -461,7 +518,8 @@ "name": "Turkmen", "localName": "türkmen", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "th", @@ -469,7 +527,8 @@ "name": "Thai", "localName": "ภาษาไทย", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "tl", @@ -477,7 +536,8 @@ "name": "Tagalog", "localName": "Tagalog", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "tr", @@ -485,7 +545,8 @@ "name": "Turkish", "localName": "Türkçe", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "tw", @@ -493,7 +554,8 @@ "name": "Akan, Twi", "localName": "Twi", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "uk", @@ -501,7 +563,8 @@ "name": "Ukrainian", "localName": "Українська", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "ur", @@ -509,7 +572,8 @@ "name": "Urdu", "localName": "اردو", "langDir": "rtl", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "uz", @@ -517,7 +581,8 @@ "name": "Uzbek", "localName": "O'zbekcha", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "vi", @@ -525,7 +590,8 @@ "name": "Vietnamese", "localName": "Tiếng Việt", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "yo", @@ -533,7 +599,8 @@ "name": "Yoruba", "localName": "Yorùbá", "langDir": "ltr", - "dateFormat": "MM/DD/YYYY" + "dateFormat": "MM/DD/YYYY", + "validISO639_1": true }, { "code": "zh-tw", @@ -541,7 +608,8 @@ "name": "Chinese Traditional", "localName": "繁體中文", "langDir": "ltr", - "dateFormat": "YYYY-MM-DD" + "dateFormat": "YYYY-MM-DD", + "validISO639_1": true }, { "code": "zh", @@ -549,6 +617,7 @@ "name": "Chinese Simplified", "localName": "简体中文", "langDir": "ltr", - "dateFormat": "YYYY-MM-DD" + "dateFormat": "YYYY-MM-DD", + "validISO639_1": true } ] diff --git a/package.json b/package.json index d91a847e531..d8a72cd73c1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ethereum-org-website", - "version": "10.3.0", + "version": "10.4.0", "license": "MIT", "private": true, "scripts": { diff --git a/public/content/ai-agents/index.md b/public/content/ai-agents/index.md index eb8e8a4100b..1fdc637e6cf 100644 --- a/public/content/ai-agents/index.md +++ b/public/content/ai-agents/index.md @@ -19,7 +19,7 @@ buttons: isSecondary: false --- -Imagine navigating Ethereum with an AI assistant that studies on-chain market trends 24/7, answers questions, and even executes transactions on your behalf. Welcome to the world of AI Agents—intelligent systems designed to simplify your digital life. +Imagine navigating Ethereum with an AI assistant that studies onchain market trends 24/7, answers questions, and even executes transactions on your behalf. Welcome to the world of AI Agents—intelligent systems designed to simplify your digital life. On Ethereum, we’re seeing innovations of AI agents ranging from virtual influencers and autonomous content creators to real-time market analysis platforms, empowering users by delivering insights, entertainment, and operational efficiency. @@ -39,7 +39,7 @@ In contrast, Ethereum's decentralized ecosystem offers several key advantages: These factors transform AI agents from simple bots into dynamic, self-improving systems that offer significant value across multiple sectors: - + diff --git a/public/content/developers/docs/data-structures-and-encoding/rlp/index.md b/public/content/developers/docs/data-structures-and-encoding/rlp/index.md index 04bc25d3032..890c86d8cc4 100644 --- a/public/content/developers/docs/data-structures-and-encoding/rlp/index.md +++ b/public/content/developers/docs/data-structures-and-encoding/rlp/index.md @@ -62,7 +62,7 @@ def encode_length(L, offset): elif L < 256**8: BL = to_binary(L) return chr(len(BL) + offset + 55) + BL - raise Exception("input too long") + raise Exception("input too long") def to_binary(x): if x == 0: diff --git a/public/content/developers/docs/programming-languages/javascript/index.md b/public/content/developers/docs/programming-languages/javascript/index.md index 5813464e560..27842c5e67c 100644 --- a/public/content/developers/docs/programming-languages/javascript/index.md +++ b/public/content/developers/docs/programming-languages/javascript/index.md @@ -46,17 +46,15 @@ This will help you understand things like "what's the data structure of an accou If you prefer to read code, this JavaScript could be a great alternative to reading through our docs. -**Check out the monorepo** -[`ethereumjs`](https://github.com/ethereumjs/ethereumjs-vm) +**Check out the EVM** +[`@ethereumjs/evm`](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/evm) ### Nodes and clients {#nodes-and-clients} An Ethereumjs client is in active development that lets you dig into how Ethereum clients work in a language you understand; JavaScript! -It used to be housed in a standalone [`repository`](https://github.com/ethereumjs/ethereumjs-client), however, was later merged into the EthereumVM monorepo as a package. - **Check out the client** -[`ethereumjs-client`](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/client) +[`@ethereumjs/client`](https://github.com/ethereumjs/ethereumjs-monorepo/tree/master/packages/client) ## Other projects {#other-projects} diff --git a/public/content/developers/docs/programming-languages/python/index.md b/public/content/developers/docs/programming-languages/python/index.md index 82b42ebc123..6d3aa1f9c86 100644 --- a/public/content/developers/docs/programming-languages/python/index.md +++ b/public/content/developers/docs/programming-languages/python/index.md @@ -84,7 +84,7 @@ Looking for more resources? Check out [ethereum.org/developers](/developers/). The following Ethereum-based projects use tools mentioned on this page. The related open-source repositories serve as a good reference for example code and best practices. - [Yearn Finance](https://yearn.finance/) and [Yearn Vault Contracts repository](https://github.com/yearn/yearn-vaults) -- [Curve](https://curve.fi/) and [Curve smart contracts repository](https://github.com/curvefi/curve-contract) +- [Curve](https://www.curve.finance/) and [Curve smart contracts repository](https://github.com/curvefi/curve-contract) - [BadgerDAO](https://badger.com/) and [smart contracts using Brownie toolchain](https://github.com/Badger-Finance/badger-system) - [Sushi](https://sushi.com/) uses [Python in managing and deploying their vesting contracts](https://github.com/sushiswap/sushi-vesting-protocols) - [Alpha Finance](https://alphafinance.io/), of Alpha Homora fame, uses [Brownie to test and deploy smart contracts](https://github.com/AlphaFinanceLab/alpha-staking-contract) diff --git a/public/content/developers/docs/storage/index.md b/public/content/developers/docs/storage/index.md index 4e57b3f297e..ded3aaebe68 100644 --- a/public/content/developers/docs/storage/index.md +++ b/public/content/developers/docs/storage/index.md @@ -43,7 +43,7 @@ In most cases, instead of storing all data onchain, the hash of where the data i Platforms with contract-based persistence: - [Filecoin](https://docs.filecoin.io/about-filecoin/what-is-filecoin/) -- [Skynet](https://siasky.net/) +- [Skynet](https://sia.tech/) - [Storj](https://storj.io/) - [Züs](https://zus.network/) - [Crust Network](https://crust.network) @@ -126,11 +126,11 @@ Proof-of-stake based: - [Documentation](https://docs.storj.io/) - [GitHub](https://github.com/storj/storj) -**Skynet - _Skynet is a decentralized PoW chain dedicated to a decentralized web._** +**Sia - _Harnesseses cryptography to create a trustless cloud storage marketplace, allowing buyers and sellers to transact directly._** -- [Skynet.net](https://siasky.net/) -- [Documentation](https://siasky.net/docs/) -- [GitHub](https://github.com/SkynetLabs/) +- [Skynet.net](https://sia.tech/) +- [Documentation](https://docs.sia.tech/) +- [GitHub](https://github.com/SiaFoundation/) **Filecoin - _Filecoin was created from the same team behind IPFS. It is an incentive layer on top of the IPFS ideals._** diff --git a/public/content/developers/tutorials/the-graph-fixing-web3-data-querying/index.md b/public/content/developers/tutorials/the-graph-fixing-web3-data-querying/index.md index 6f9e0e95a55..277da463a7b 100644 --- a/public/content/developers/tutorials/the-graph-fixing-web3-data-querying/index.md +++ b/public/content/developers/tutorials/the-graph-fixing-web3-data-querying/index.md @@ -3,14 +3,7 @@ title: "The Graph: Fixing Web3 data querying" description: Blockchain is like a database but without SQL. All the data is there, but no way to access it. Let me show you how to fix this with The Graph and GraphQL. author: Markus Waas lang: en -tags: - [ - "solidity", - "smart contracts", - "querying", - "the graph", - "react", - ] +tags: ["solidity", "smart contracts", "querying", "the graph", "react"] skill: intermediate published: 2020-09-06 source: soliditydeveloper.com @@ -90,7 +83,7 @@ First let's talk about GraphQL, originally designed and implemented by Facebook. ![GraphQL API vs. REST API](./graphql.jpg) - +![](./graphql-query.gif) The two images pretty much capture the essence of GraphQL. With the query on the right we can define exactly what data we want, so there we get everything in one request and nothing more than exactly what we need. A GraphQL server handles the fetching of all data required, so it is incredibly easy for the frontend consumer side to use. [This is a nice explanation](https://www.apollographql.com/blog/graphql-explained-5844742f195e/) of how exactly the server handles a query if you're interested. diff --git a/public/content/enterprise/index.md b/public/content/enterprise/index.md index b04cdf02805..605f69e2d96 100644 --- a/public/content/enterprise/index.md +++ b/public/content/enterprise/index.md @@ -86,7 +86,7 @@ Here are some of the enterprise applications that have been built on top of the - [ABN AMRO](https://tokeny.com/tokeny-fuels-abn-amro-bank-in-tokenizing-green-bonds-on-polygon/) - _with Tokeny, tokenized green bonds_ - [Anvil](https://anvil.xyz/) - _a system of Ethereum-based smart contracts that manages collateral and issues fully secured credit_ - [Mata Capital](https://consensys.io/blockchain-use-cases/finance/mata-capital) - _real estate investment tokenization_ -- [Obligate](https://www.obligate.com/) - _regulated and KYC'd on-chain bonds and commercial paper_ +- [Obligate](https://www.obligate.com/) - _regulated and KYC'd onchain bonds and commercial paper_ - [Siemens](https://press.siemens.com/global/en/pressrelease/siemens-remains-pioneer-another-digital-bond-successfully-issued-blockchain) - _bond issuance_ - [Sila](https://silamoney.com/) - _banking and ACH payments infrastructure-as-a-service, using a stablecoin_ - [Societe Generale FORGE](https://www.sgforge.com/product/bonds/) - _bond issuance_ diff --git a/public/content/guides/how-to-swap-tokens/index.md b/public/content/guides/how-to-swap-tokens/index.md index 16045a62ef0..44a8b36d119 100644 --- a/public/content/guides/how-to-swap-tokens/index.md +++ b/public/content/guides/how-to-swap-tokens/index.md @@ -22,7 +22,7 @@ Some popular exchanges are: - [Uniswap](https://app.uniswap.org/#/swap) - [Sushiswap](https://www.sushi.com/swap) - [1Inch](https://app.1inch.io/#/1/unified/swap/ETH/DAI) -- [Curve](https://curve.fi/#/ethereum/swap) +- [Curve](https://www.curve.finance/dex/ethereum/swap/) Interesting? Learn more about what [decentralised finance (DeFi)](/defi/) is and how these new kinds of exchanges work. diff --git a/public/content/guides/index.md b/public/content/guides/index.md index 79c5f6f5d1c..e5d7f33d7b1 100644 --- a/public/content/guides/index.md +++ b/public/content/guides/index.md @@ -18,7 +18,7 @@ Do you want to start your Ethereum journey? Our practical guides lead you step-b 1. [How to revoke smart contract access to your crypto funds](/guides/how-to-revoke-token-access/) - If you suddenly see a transaction in your wallet that you did not initiate, this guide will teach you how to prevent that from happening again. -2. [How to identify scam tokens](/guides/how-to-id-scam-tokens/) - What are scam tokens, how do they make themselves look legitimate, and how to identify them to protect yourself and avoid being scammed. +2. [How to identify scam tokens](/guides/how-to-id-scam-tokens/) - What are scam tokens? How do they make themselves look legitimate, and how do you identify them to protect yourself and avoid being scammed? ## Using Ethereum diff --git a/public/content/payments/index.md b/public/content/payments/index.md index 88d93f2af25..72569c2908c 100644 --- a/public/content/payments/index.md +++ b/public/content/payments/index.md @@ -27,7 +27,7 @@ This isn't a far-off dream – it's happening today on Ethereum. While tradition For millions of people working abroad, sending money back home is a regular necessity. Traditional remittance services often come with high fees and slow processing times. Ethereum offers a compelling alternative. - + diff --git a/public/content/prediction-markets/index.md b/public/content/prediction-markets/index.md index d1d0d79c49d..bfd97221f07 100644 --- a/public/content/prediction-markets/index.md +++ b/public/content/prediction-markets/index.md @@ -30,7 +30,7 @@ In theory, because bettors stand to profit from being correct, prediction market Unlike traditional forecasting, blockchain-based prediction markets are: - + diff --git a/public/content/real-world-assets/index.md b/public/content/real-world-assets/index.md new file mode 100644 index 00000000000..00e72bfde6f --- /dev/null +++ b/public/content/real-world-assets/index.md @@ -0,0 +1,95 @@ +--- +title: Real-world assets (RWAs) +metaTitle: What are RWAs? | Benefits and Use of Real-world assets +description: An overview of Real-world assets on Ethereum +lang: en +template: use-cases +emoji: ":house_buildings:" +image: /images/man-and-dog-playing.png +alt: Man and dog playing. +sidebarDepth: 2 +summaryPoint1: A method for turning valuable commodities into digital tokens. +summaryPoint2: You can now own portions of real-life objects or assets, rather than having to buy an entire property or item. +summaryPoint3: Connects traditional finance with the blockchain ecosystem. +--- + +Real-world assets (RWAs) are tokens representing existing forms of wealth, such as real estate, gold, stocks, art, machinery or collectibles. Tokenizing these items translates them into digital form, allowing them to be divided between multiple owners and making it easier to trade them. + +## What are RWAs? {#what-are-rwas} + +Some RWAs are tangible—items you can see and touch, such as gold bars or commercial buildings. Others are intangible, such as government debt, intellectual property, or equity in a company. + +When tokenized, these assets are turned into units of value. Tokenized gold is a good example of how this works. The company [Paxos](https://www.paxos.com/) translates 400-ounce gold bars into 400 tokens on the Ethereum blockchain, each backed by one ounce of gold. Token-holders can redeem their tokens for gold at any point. That’s also the case for tokens purchased from another RWA company, [Tether Gold](https://gold.tether.to/). + +Each token can be divided into even smaller fractions. Tether Gold tokens, for instance, can be split into parts as small as 0.000001. + +RWA tokens don’t have any intrinsic value. Rather, they reflect the value of the item that they represent, and so the token’s value changes along with the item’s value. + +## What are the benefits of RWAs? {#rwas-benifits} + + + + + + + + + + +## How do RWAs work? {#how-rwas-work} + +Let’s look at a few examples from across the RWA ecosystem: real estate, traditional financial products, and fine art. + +### Investing in real estate {#investing-in-real-estate} + +Say that you’d like to invest in real estate, but purchasing an entire property is out of reach. Instead, you could buy RWAs through a project such as [RealT](https://realt.co/). Its tokens represent shares in a limited liability company (LLC) created to hold a property’s deed. Token-holders receive rental income in the form of stablecoins according to the fraction they hold; RealT says it has so far returned $15 million USD in net rental income to investors. + +Another project along the same lines, [LABS Group](https://x.com/labsgroupio), allows people to buy into tokenized real estate with amounts as small as $100 USD. + +### Investing in financial products {#investing-in-financial-products} + +Several projects bridge the world of traditional finance and decentralized finance (DeFi) by bringing securities, stocks, bonds and other financial instruments onto the blockchain. + +For example, the Ethereum-based company [Securitize](https://securitize.io/) specializes in tokenizing traditional financial products. In 2024, it partnered with BlackRock to launch a RWA fund. BlackRock says it plans to eventually tokenize $10 trillion USD of its assets: its CEO, Larry Fink, called tokenization “the next generation for markets”. + +### Investing in fine art {#investing-in-fine-art} + +There are a few different mechanisms for fine-art investment. [Masterworks](https://www.masterworks.com/) buys artwork, securitizes each piece, and sells shares in the form of tokens. It plans to later sell the artwork and distribute the profits to token-holders. + +Art owners looking to capitalize on their collection can sell up to 49 per cent of an artwork’s value on the platform [Maecenas](https://www.maecenas.co/), provided the piece is valued at more than $1 million USD. + +In both cases, token-holders don’t control the storage or future sale of the artwork. Rather, they’re in charge of how long they hold onto their tokens, which rise and fall with the value of the art. + +Meanwhile, the blockchain-based digital art registry [Artory](https://www.artory.com/) verifies the authenticity of artworks and records past ownership. + +### Investing in collectibles {#investing-in-collectibles} + +So far, most of these examples demonstrate how tokenization allows partial ownership of various types of wealth. Another benefit of tokenization is that it enables the trade of valuable items, such as collectibles, on the global market. + +One example of this is [Courtyard](https://courtyard.io/), which tokenizes trading cards–think of baseball cards, football cards or Pokemon cards. Card owners ship their cards to a secure storage facility in the USA. The cards are minted as digital tokens and added to the owners’ wallets for trading on Courtyard’s marketplace. Courtyard only accepts graded cards: that’s where a third party has certified a card’s authenticity and awarded it a score based on its condition, whether dilapidated or pristine. + +Courtyard also offers a type of royalty scheme. Each time a card is sold, the person who tokenized it receives one percent of the revenue. Only card originators are rewarded in this way. At any point, an owner can swap their digital cards for physical cards, no matter where they are located in the world. + +## What are the limitations of RWAs? {#rwas-limitations} + +One of the challenges of RWAs, at this early stage, involves ensuring the connection between real-life objects and their digital representations. + +A green flag is when RWA projects supply investors with proof of reserves–the guarantee that they are the legal owners of the physical objects backing digital tokens. Think of Paxos, Tether Gold, or Courtyard, mentioned earlier, all of which hold their assets in secure storage and offer owners the option of swapping a token for its physical equivalent at any point. + +Another limitation is whether token ownership is recognised by legal systems around the world. In other words, are smart contracts enforceable in a court of law, or can the holder of a RWA token claim ownership of the real-life item? + +Some of the frontrunners in terms of setting up legal frameworks specifically to recognise tokenization include Singapore, the United Arab Emirates, Hong Kong, and Switzerland, which introduced legislation in 2021 nicknamed the ‘Blockchain Act’ to regulate technologies such as tokenization. The European Union has begun the process of regulating RWAs, while in the United States, the Securities and Exchange Commission is expected to issue guidance on RWAs at some point. + +## Learn more {#learn-more} + +Dive into [smart contracts](/smart-contracts/) or find out more about a different token class, [non-fungible tokens (NFT)](/nft/). + +## Further reading {#further-reading} + +- [What is asset tokenization?](https://www.britannica.com/money/real-world-asset-tokenization) on Britannica +- [How tokenization is transforming global finance and investment](https://www.weforum.org/stories/2024/12/tokenization-blockchain-assets-finance/) on the World Economic Forum +- [What crypto investors need to know about tokenizing real-world assets](https://www.forbes.com/sites/irinaheaver/2024/03/14/what-crypto-investors-need-to-know-about-tokenizing-real-world-assets/) on Forbes +- [How smart contracts work with blockchain](https://www.britannica.com/money/how-smart-contracts-work) on Britannica +- [How tokenized real-world assets are transforming DeFi](https://medium.com/coinmonks/how-tokenized-real-world-assets-are-transforming-defi-4e040f28732a) on Medium +- [What is RWA in crypto? Its blockchain role explained](https://www.bitdegree.org/crypto/tutorials/what-is-rwa-in-crypto) on BitDegree +- [Top real-world assets (RWAs) coins today by market cap](https://www.forbes.com/digital-assets/categories/real-world-assets-rwa/) on Forbes \ No newline at end of file diff --git a/public/content/roadmap/pectra/7702/index.md b/public/content/roadmap/pectra/7702/index.md index e03838a19ce..f6d04c7601e 100644 --- a/public/content/roadmap/pectra/7702/index.md +++ b/public/content/roadmap/pectra/7702/index.md @@ -57,7 +57,7 @@ For more information: **Avoiding Vendor Lock-In**: In line with the above, a good implementation is vendor-neutral and interoperable. This often means adhering to emerging standards for smart accounts. For instance, [Alchemy’s Modular Account](https://github.com/alchemyplatform/modular-account) uses the ERC-6900 standard for modular smart accounts and is designed with “permissionless interoperable usage” in mind. -**Privacy Preservation**: While on-chain privacy is limited, a delegation contract should strive to minimize data exposure and linkability. This can be achieved by supporting features like gas payments in ERC-20 tokens (so users need not maintain a public ETH balance, which improves privacy and UX) and one-time session keys (which reduce reliance on a single long-term key). For example, EIP-7702 enables paying gas in tokens via sponsored transactions, and a good implementation will make it easy to integrate such paymasters without leaking more information than necessary. Additionally, off-chain delegation of certain approvals (using signatures that are verified on-chain) means fewer on-chain transactions with the user’s primary key, aiding privacy. Accounts that require using a relayer force users to reveal their IP addresses. PublicMempools improves this, when a transaction/UserOp propagates through the mempool you can't tell whether it originated from the IP that sent it, or just relayed through it via the p2p protocol. +**Privacy Preservation**: While onchain privacy is limited, a delegation contract should strive to minimize data exposure and linkability. This can be achieved by supporting features like gas payments in ERC-20 tokens (so users need not maintain a public ETH balance, which improves privacy and UX) and one-time session keys (which reduce reliance on a single long-term key). For example, EIP-7702 enables paying gas in tokens via sponsored transactions, and a good implementation will make it easy to integrate such paymasters without leaking more information than necessary. Additionally, off-chain delegation of certain approvals (using signatures that are verified onchain) means fewer onchain transactions with the user’s primary key, aiding privacy. Accounts that require using a relayer force users to reveal their IP addresses. PublicMempools improves this, when a transaction/UserOp propagates through the mempool you can't tell whether it originated from the IP that sent it, or just relayed through it via the p2p protocol. **Extensibility and Modular Security**: Account implementations should be extensible so they can evolve with new features and security improvements. Upgradability is inherently possible with EIP-7702 (since an EOA can always delegate to a new contract in the future to upgrade its logic). Beyond upgradability, a good design allows modularity – e.g. plug-in modules for different signature schemes or spending policies – without needing to redeploy entirely. Alchemy’s Account Kit is a prime example, allowing developers to install validation modules (for different signature types like ECDSA, BLS, etc.) and execution modules for custom logic. To achieve greater flexibility and security in EIP-7702-enabled accounts, developers are encouraged to delegate to a proxy contract rather than directly to a specific implementation. This approach allows for seamless upgrades and modularity without requiring additional EIP-7702 authorizations for each change. @@ -108,7 +108,7 @@ By adopting these solutions, developers can enhance the security of EIP-7702 del When users perform delegated signatures, the target contract receiving the delegation should be clearly and prominently displayed to help mitigate phishing risks. -**Minimal Trusted Surface & Security**: While offering flexibility, a delegation contract should keep its core logic minimal and auditable. The contract is effectively an extension of the user’s EOA, so any flaw can be catastrophic. Implementations should follow best practices from the smart contract security community. For instance, constructor or initializer functions must be carefully secured – as highlighted by Alchemy, if using a proxy pattern under 7702, an unprotected initializer could let an attacker take over the account. Teams should aim to keep the on-chain code simple: Ambire’s 7702 contract is only ~200 lines of Solidity, deliberately minimizing complexity to reduce bugs. A balance must be struck between feature-rich logic and the simplicity that eases auditing. +**Minimal Trusted Surface & Security**: While offering flexibility, a delegation contract should keep its core logic minimal and auditable. The contract is effectively an extension of the user’s EOA, so any flaw can be catastrophic. Implementations should follow best practices from the smart contract security community. For instance, constructor or initializer functions must be carefully secured – as highlighted by Alchemy, if using a proxy pattern under 7702, an unprotected initializer could let an attacker take over the account. Teams should aim to keep the onchain code simple: Ambire’s 7702 contract is only ~200 lines of Solidity, deliberately minimizing complexity to reduce bugs. A balance must be struck between feature-rich logic and the simplicity that eases auditing. ### Known implementations {#known-implementations} diff --git a/public/content/roadmap/pectra/maxeb/index.md b/public/content/roadmap/pectra/maxeb/index.md index ee9f2de25e0..a623a7fe529 100644 --- a/public/content/roadmap/pectra/maxeb/index.md +++ b/public/content/roadmap/pectra/maxeb/index.md @@ -129,17 +129,28 @@ Note: The signing is done by the withdrawal address, not the validator key. Validators with **Type 1** credentials get automatic, gasless sweeps of their excess balance (anything over 32 ETH) to their withdrawal address. Because **Type 2** allows a validator to compound balances in 1 ETH increments, it will not automatically sweep balances until it reaches 2048 ETH. Partial withdrawals on **Type 2** validators must be manually triggered and will cost gas. +## Consolidation tooling {#consolidation-tooling} + +There are several tools available to manage consolidations. The official tool, created by the Ethereum Foundation, is the [Launchpad](https://launchpad.ethereum.org/en/validator-actions). There are also third-party tools created by entities from the staking community that may offer features not provided by the Launchpad. While the tools here are not audited or endorsed by the Ethereum Foundation, the following are open source tools by known members of the community. + +| Tool | Website | Open source | Creator | Audited | Interface | Notable features | +| --- | --- | --- | --- | --- | --- | --- | +| Pectra Staking Manager | pectrastaking.com | Yes, Apache 2.0 | [Pier Two](https://piertwo.com/) | No | Web UI | Wallet Connect, works with SAFE | +| Pectra Validator Ops CLI Tool | [GitHub](https://github.com/Luganodes/Pectra-Batch-Contract) | Yes, MIT | [Luganodes](https://www.luganodes.com/) | Yes, Quantstamp [May 2025](https://certificate.quantstamp.com/full/luganodes-pectra-batch-contract/23f0765f-969a-4798-9edd-188d276c4a2b/index.html) | Command line | Batching, for many validators at once | +| Ethereal | [GitHub](https://github.com/wealdtech/ethereal) | Yes, Apache 2.0 | [Jim McDonald](https://www.attestant.io/team/) | No | Command line | Full feature set for validator and node management | +| Siren | [GitHub](https://github.com/sigp/siren) | Yes, Apache 2.0 | [Sigma Prime](https://sigmaprime.io/) | No | Some command line, but primarily web UI | Only works if you're using the Lighthouse consensus client | + ## FAQ {#FAQ} -### **Does opting-in change my proposal luck or rewards?** +### Does opting-in change my proposal luck or rewards? No. Opting in does not decrease your change of proposal - your duties and proposal selection remain the same. For example, if you have two 32 ETH validators vs one 64 ETH validator, you will have the same total chances of being selected to propose a block and earn rewards. -### **Does opting in change my slashing risk?** +### Does opting in change my slashing risk? For smaller or unprofessional operators, the short answer is no. The longer answer is that, for professional operators running many validators per node with fast alerting, consolidating into fewer validators may reduce their ability to react to a slashing and prevent cascade events. The initial slashing *penalty* for all validators has been dramatically reduced from 1 ETH (per 32 ETH) to 0.0078125 ETH (per 32 ETH) to offset this risk. -### **Do I have to exit my validator to convert?** +### Do I have to exit my validator to convert? No. You can convert in place without exiting. @@ -175,13 +186,17 @@ Automatic sweeps will only happen with excess balances over 2048. For all other No. Converting to **Type 2** is irreversible. +### If I want to consolidate multiple validators, do I have convert each one to Type 2 first? + +Nope! Convert one validator to Type 2 then use that as the target. All other validators consolidated into that Type 2 target can be Type 1 or Type 2 + ### My validator is offline or below 32 ETH - can I still convert it? Yes. As long as it's active (not exited) and you can sign with its withdrawal address, you can convert it. ## Resources {#resources} -- [Electra consensus specs](https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md): This is the ‘truest' version that you should rely on. When in doubt, read the specs +- [Electra consensus specs](https://github.com/ethereum/consensus-specs/blob/dev/specs/electra/beacon-chain.md): This is the 'truest' version that you should rely on. When in doubt, read the specs - Not everybody is comfortable wading through code, so [this maxEB-GPT](https://chatgpt.com/g/g-67f1650fb48081918f555e0c8d1c2ae9-maxeb-gpt) can help interpret the specs. *Disclaimer: The specs, not the AI, should be relied on as truth, as the AI may misinterpret information or hallucinate answers* - [pectrified.com](https://pectrified.com/): View the state of consolidations, deposits, and queue waiting times - [Ethereal](https://github.com/wealdtech/ethereal): Community-created CLI tool for managing common validator tasks diff --git a/public/content/translations/pt-br/whitepaper/index.md b/public/content/translations/pt-br/whitepaper/index.md index eb28d22bece..9d6c8cabfd9 100644 --- a/public/content/translations/pt-br/whitepaper/index.md +++ b/public/content/translations/pt-br/whitepaper/index.md @@ -405,7 +405,7 @@ Conforme descrito na seção de transição de estado, nossa solução funciona - Um invasor vê um contrato com um código como `send(A,contract.storage[A]); contract.storage[A] = 0`, e envia uma transação com gas o suficiente para executar a primeira etapa, mas não a segunda (ou seja, fazendo uma retirada mas sem deixar diminuir o saldo). O autor do contrato não precisa se preocupar em se proteger contra ataques assim porque, se a execução parar no meio do caminho, as mudanças são revertidas. - Um contrato financeiro funciona tomando a mediana de nove feeds de dados proprietários para minimizar o risco. Um invasor controla um dos feed de dados, que é projetado para ser modificável por meio do mecanismo de chamada por endereço-variável descrito na seção sobre DAOs, e o converte para rodar um loop infinito, forçando assim qualquer tentativa de reivindicar fundos do contrato financeiro a ficar sem gas. Porém, o contrato financeiro pode definir um limite de gas na mensagem para prevenir este problema. -A alternativa para completude de Turing é a incompletude de Turing, em que `JUMP` e `JUMPI` não existem e apenas uma cópia de cada contrato pode existir no stack de chamadas em dado momento. Com esse sistema, o sistema de taxas descrito e as incertezas em torno da eficácia de nossa solução podem não ser necessárias, pois o custo de executar um contrato seria limitado por seu tamanho. Alpem disso, a incompletude deTuring não é uma limitação tão grande. De todos os exemplos de contrato que concebemos internamente, até agora apenas um precisou de um loop, e mesmo esse loop poderia ser removido fazendo 26 repetições de um pedaço de linha de código. Dadas as sérias implicações da completude de Turing e o benefício limitado, por que não simplesmente ter uma linguagem Turing incompleta? Na verdade, porém, a incompletude de Turing está longe de ser uma solução perfeita para o problema. Para entender o porquê, considere os seguintes contratos: +A alternativa para completude de Turing é a incompletude de Turing, em que `JUMP` e `JUMPI` não existem e apenas uma cópia de cada contrato pode existir no stack de chamadas em dado momento. Com esse sistema, o sistema de taxas descrito e as incertezas em torno da eficácia de nossa solução podem não ser necessárias, pois o custo de executar um contrato seria limitado por seu tamanho. Além disso, a incompletude de Turing não é uma limitação tão grande. De todos os exemplos de contrato que concebemos internamente, até agora apenas um precisou de um loop, e mesmo esse loop poderia ser removido fazendo 26 repetições de um pedaço de linha de código. Dadas as sérias implicações da completude de Turing e o benefício limitado, por que não simplesmente ter uma linguagem Turing incompleta? Na verdade, porém, a incompletude de Turing está longe de ser uma solução perfeita para o problema. Para entender o porquê, considere os seguintes contratos: ```sh C0: call(C1); call(C1); @@ -450,7 +450,7 @@ O modelo de emissão será o seguinte: _Apesar da emissão linear da moeda, assim como com o Bitcoin, ao longo do tempo a taxa de crescimento da oferta tende a zero._ -As duas principais opções no modelo acima são (1) a existência e o tamanho de um pool de doações e (2) a existência de uma oferta de crescimento linear permanente, em vez de uma oferta limitada como no Bitcoin. A justificativa do pool de doações é a seguinte. se o pool de doações não existisse e a emissão linear fosse reduzida a 0,217x para fornecer a mesma taxa de inflação, então a quantidade total de ether seria 16,5% menor e então cada unidade seria 19,8% mais valiosa. Assim, no equilíbrio de 19,8%, mais ether seria comprado na venda, então cada unidade voltaria a ter exatamente o mesmo valor de antes. A organização teria então também 1,198% mais BTC, que provavelmente será dividido em duas fatias: o BTC original e o adicional de 0,198x. Assim, a situação equivale _exatamente_ à doação, mas com uma importante diferença: a organização possui apenas BTC, e portanto, não é incentivada a apoiar o valor da unidade ether. +As duas principais opções no modelo acima são (1) a existência e o tamanho de um pool de doações e (2) a existência de uma oferta de crescimento linear permanente, em vez de uma oferta limitada como no Bitcoin. A justificativa do pool de doações é a seguinte. Se o pool de doações não existisse e a emissão linear fosse reduzida a 0,217x para fornecer a mesma taxa de inflação, então a quantidade total de ether seria 16,5% menor e então cada unidade seria 19,8% mais valiosa. Assim, no equilíbrio de 19,8%, mais ether seria comprado na venda, então cada unidade voltaria a ter exatamente o mesmo valor de antes. A organização teria então também 1,198% mais BTC, que provavelmente será dividido em duas fatias: o BTC original e o adicional de 0,198x. Assim, a situação equivale _exatamente_ à doação, mas com uma importante diferença: a organização possui apenas BTC, e portanto, não é incentivada a apoiar o valor da unidade ether. O modelo linear permanente de crescimento da oferta (de moeda) reduz o risco do que alguns veem como uma excessiva concentração de riqueza em Bitcoin, e dá aos indivíduos, agora e no futuro, uma chance justa de adquirir unidades da moeda, enquanto mantém um forte incentivo para obter e manter ether, porque a "taxa de crescimento da oferta" como porcentagem ainda tende a zero ao longo do tempo. Também teorizamos isso porque as moedas sempre se perdem ao longo do tempo devido a descuidos, morte etc, e a perda de moedas pode ser modelada como uma porcentagem da oferta total por ano, que a oferta total de moedas em circulação acabará por estabilizar num valor igual à emissão anual dividida pela taxa de perda (por exemplo, a uma taxa de perda de 1%, quando a oferta atingir 26x então 0,26x será minerado e 0,26x perdido todo ano, criando um equilíbrio). diff --git a/public/images/resources/beaconcha-in.png b/public/images/resources/beaconcha-in.png index 58cb0f5e3cf..93ea40b0c30 100644 Binary files a/public/images/resources/beaconcha-in.png and b/public/images/resources/beaconcha-in.png differ diff --git a/public/images/resources/blobsguru.png b/public/images/resources/blobsguru.png index 19ce09eb8c8..b5133b1f3a4 100644 Binary files a/public/images/resources/blobsguru.png and b/public/images/resources/blobsguru.png differ diff --git a/public/images/resources/blocknative.png b/public/images/resources/blocknative.png index 3c91bce7722..8f08c5909ed 100644 Binary files a/public/images/resources/blocknative.png and b/public/images/resources/blocknative.png differ diff --git a/public/images/resources/bundlebear.png b/public/images/resources/bundlebear.png new file mode 100644 index 00000000000..5faef4b348a Binary files /dev/null and b/public/images/resources/bundlebear.png differ diff --git a/public/images/resources/cryptowerk.png b/public/images/resources/cryptowerk.png index efc0405bb9a..09af7a68e30 100644 Binary files a/public/images/resources/cryptowerk.png and b/public/images/resources/cryptowerk.png differ diff --git a/public/images/resources/defi-llama.png b/public/images/resources/defi-llama.png index c0f44698920..87b01828f69 100644 Binary files a/public/images/resources/defi-llama.png and b/public/images/resources/defi-llama.png differ diff --git a/public/images/resources/defi-market-cap.png b/public/images/resources/defi-market-cap.png deleted file mode 100644 index 64371a8c8ef..00000000000 Binary files a/public/images/resources/defi-market-cap.png and /dev/null differ diff --git a/public/images/resources/defi-scan.png b/public/images/resources/defi-scan.png index c10eca423be..58c0ffd9ac4 100644 Binary files a/public/images/resources/defi-scan.png and b/public/images/resources/defi-scan.png differ diff --git a/public/images/resources/eas.png b/public/images/resources/eas.png index f6d4b65cb45..1819852baa7 100644 Binary files a/public/images/resources/eas.png and b/public/images/resources/eas.png differ diff --git a/public/images/resources/eigenphi.png b/public/images/resources/eigenphi.png index 8cfa8873cff..f31d14e4bba 100644 Binary files a/public/images/resources/eigenphi.png and b/public/images/resources/eigenphi.png differ diff --git a/public/images/resources/eth-glyph-black.png b/public/images/resources/eth-glyph-black.png index 991c4d27aa5..5bdaca42509 100644 Binary files a/public/images/resources/eth-glyph-black.png and b/public/images/resources/eth-glyph-black.png differ diff --git a/public/images/resources/eth-glyph-blue-circle.png b/public/images/resources/eth-glyph-blue-circle.png index b811169710d..821a185c265 100644 Binary files a/public/images/resources/eth-glyph-blue-circle.png and b/public/images/resources/eth-glyph-blue-circle.png differ diff --git a/public/images/resources/eth-glyph-e-org.png b/public/images/resources/eth-glyph-e-org.png index dfe97a1db86..e56351c10cf 100644 Binary files a/public/images/resources/eth-glyph-e-org.png and b/public/images/resources/eth-glyph-e-org.png differ diff --git a/public/images/resources/eth-glyph-rainbow-frame.png b/public/images/resources/eth-glyph-rainbow-frame.png new file mode 100644 index 00000000000..fa4ff4db843 Binary files /dev/null and b/public/images/resources/eth-glyph-rainbow-frame.png differ diff --git a/public/images/resources/eth-glyph-rainbow.frame.png b/public/images/resources/eth-glyph-rainbow.frame.png deleted file mode 100644 index ff150fe28e4..00000000000 Binary files a/public/images/resources/eth-glyph-rainbow.frame.png and /dev/null differ diff --git a/public/images/resources/etherscan.png b/public/images/resources/etherscan.png index 77cba5c68c8..1d8a982bc80 100644 Binary files a/public/images/resources/etherscan.png and b/public/images/resources/etherscan.png differ diff --git a/public/images/resources/ethproofs.png b/public/images/resources/ethproofs.png index d40c4730fff..f2a724f188d 100644 Binary files a/public/images/resources/ethproofs.png and b/public/images/resources/ethproofs.png differ diff --git a/public/images/resources/ethstaker.png b/public/images/resources/ethstaker.png deleted file mode 100644 index ad100752a55..00000000000 Binary files a/public/images/resources/ethstaker.png and /dev/null differ diff --git a/public/images/resources/farcaster.png b/public/images/resources/farcaster.png index e063ce20dc6..c07d58ef015 100644 Binary files a/public/images/resources/farcaster.png and b/public/images/resources/farcaster.png differ diff --git a/public/images/resources/growthepie.png b/public/images/resources/growthepie.png index 1a633a3b216..fc759f38419 100644 Binary files a/public/images/resources/growthepie.png and b/public/images/resources/growthepie.png differ diff --git a/public/images/resources/l2beat.png b/public/images/resources/l2beat.png index 66d6a7ab17e..7cedb97556a 100644 Binary files a/public/images/resources/l2beat.png and b/public/images/resources/l2beat.png differ diff --git a/public/images/resources/lucky-staker.png b/public/images/resources/lucky-staker.png new file mode 100644 index 00000000000..60e0cfbb36b Binary files /dev/null and b/public/images/resources/lucky-staker.png differ diff --git a/public/images/resources/mev-watch.png b/public/images/resources/mev-watch.png deleted file mode 100644 index ae7257c4cc0..00000000000 Binary files a/public/images/resources/mev-watch.png and /dev/null differ diff --git a/public/images/resources/nftgo.png b/public/images/resources/nftgo.png index d2deb18179c..45e12bfeabf 100644 Binary files a/public/images/resources/nftgo.png and b/public/images/resources/nftgo.png differ diff --git a/public/images/resources/nodewatch.png b/public/images/resources/nodewatch.png index 3c7927d38a6..6f7d8cc254a 100644 Binary files a/public/images/resources/nodewatch.png and b/public/images/resources/nodewatch.png differ diff --git a/public/images/resources/panda.png b/public/images/resources/panda.png new file mode 100644 index 00000000000..e8755ed76df Binary files /dev/null and b/public/images/resources/panda.png differ diff --git a/public/images/resources/pectrified.png b/public/images/resources/pectrified.png new file mode 100644 index 00000000000..dfe5837e9d5 Binary files /dev/null and b/public/images/resources/pectrified.png differ diff --git a/public/images/resources/rated-network.png b/public/images/resources/rated-network.png index 606e084ce40..1f8a3036b57 100644 Binary files a/public/images/resources/rated-network.png and b/public/images/resources/rated-network.png differ diff --git a/public/images/resources/relayscan.png b/public/images/resources/relayscan.png index b8eb5155749..d0636ec4b2c 100644 Binary files a/public/images/resources/relayscan.png and b/public/images/resources/relayscan.png differ diff --git a/public/images/resources/reserves.png b/public/images/resources/reserves.png new file mode 100644 index 00000000000..13a5e5e1ee0 Binary files /dev/null and b/public/images/resources/reserves.png differ diff --git a/public/images/resources/rwa.png b/public/images/resources/rwa.png index 85d737daf0c..1fcbca6f028 100644 Binary files a/public/images/resources/rwa.png and b/public/images/resources/rwa.png differ diff --git a/public/images/resources/stablecoins-wtf.png b/public/images/resources/stablecoins-wtf.png index 32c7b8e92db..99dbbfb5ebc 100644 Binary files a/public/images/resources/stablecoins-wtf.png and b/public/images/resources/stablecoins-wtf.png differ diff --git a/public/images/resources/supermajority.png b/public/images/resources/supermajority.png index dfaf8f9262d..c76b2b45358 100644 Binary files a/public/images/resources/supermajority.png and b/public/images/resources/supermajority.png differ diff --git a/public/images/resources/visa-onchain-analytcs.png b/public/images/resources/visa-onchain-analytcs.png index 06454e4fb26..8ebebdb5205 100644 Binary files a/public/images/resources/visa-onchain-analytcs.png and b/public/images/resources/visa-onchain-analytcs.png differ diff --git a/public/images/resources/walletbeat.png b/public/images/resources/walletbeat.png new file mode 100644 index 00000000000..6555e8724c3 Binary files /dev/null and b/public/images/resources/walletbeat.png differ diff --git a/public/images/roadmap/roadmap-fusaka.png b/public/images/roadmap/roadmap-fusaka.png new file mode 100644 index 00000000000..abc542709e4 Binary files /dev/null and b/public/images/roadmap/roadmap-fusaka.png differ diff --git a/public/images/roadmap/roadmap-pectra.png b/public/images/roadmap/roadmap-pectra.png index 82a3d2a2c3f..00db6e5a20f 100644 Binary files a/public/images/roadmap/roadmap-pectra.png and b/public/images/roadmap/roadmap-pectra.png differ diff --git a/src/components/ActionCard.tsx b/src/components/ActionCard.tsx index b54d79aa26a..c6c8e4d3cef 100644 --- a/src/components/ActionCard.tsx +++ b/src/components/ActionCard.tsx @@ -41,7 +41,7 @@ const ActionCard = ({ return (
diff --git a/src/components/Codeblock.tsx b/src/components/Codeblock.tsx index f4c6989977a..dc91bdea881 100644 --- a/src/components/Codeblock.tsx +++ b/src/components/Codeblock.tsx @@ -41,10 +41,6 @@ const TopBarItem = ({ const codeTheme = { light: { - plain: { - backgroundColor: "#f7f7f7", // background-highlight (gray-50) - color: "#6C24DF", // primary (purple-600) - }, styles: [ { style: { color: "#6c6783" }, @@ -113,10 +109,6 @@ const codeTheme = { }, dark: { // Pulled from `defaultProps.theme` for potential customization - plain: { - backgroundColor: "#121212", // background-highlight (gray-900) - color: "#B38DF0", // primary (purple-400) - }, styles: [ { style: { color: "#6c6783" }, @@ -253,7 +245,7 @@ const Codeblock = ({ /* Context: https://github.com/ethereum/ethereum-org-website/issues/6202 */
{ - // TODO: LOGOS + // TODO: LOGOS, extract intl strings const productListSets = { "ai-agents": [ { @@ -40,7 +40,7 @@ const AiAgentProductLists = ({ list }: { list: string }) => { Luna engages with users constantly through own X account and live stream. You might receive an X reply if you tag her handle or a voice message if you comment on her stream and own her token! Luna - controls own on-chain wallet. + controls own onchain wallet.

,
) => ( const ContributorAvatar = ({ contributor, label, -}: ContributorProps & { label?: string }) => ( + className, +}: ContributorProps & { label?: string; className?: string }) => ( ) +const ContributorAvatarGroup = ({ + contributors, +}: { + contributors: FileContributor[] +}) => { + if (!contributors.length) return null + + const maxVisibleAvatars = contributors.length >= 3 ? 3 : contributors.length + const remainingCount = contributors.length > 3 ? contributors.length - 3 : 0 + + return ( + + {contributors.slice(0, maxVisibleAvatars).map((contributor, index) => ( +
+ +
+ ))} + {remainingCount > 0 && ( +
+ +{remainingCount} +
+ )} +
+ ) +} + type ContributorProps = { contributor: FileContributor } const Contributor = ({ contributor }: ContributorProps) => ( @@ -45,6 +81,11 @@ const Contributor = ({ contributor }: ContributorProps) => ( contributor={contributor} label={"@" + contributor.login} /> + {contributor.html_url.includes("crowdin.com") && ( +

+ +

+ )}
) @@ -52,24 +93,16 @@ type FlexProps = BaseHTMLAttributes & { asChild?: boolean } export type FileContributorsProps = FlexProps & { contributors: FileContributor[] lastEditLocaleTimestamp: string + className?: string } const FileContributors = ({ contributors, lastEditLocaleTimestamp, + className, ...props }: FileContributorsProps) => { const [isModalOpen, setModalOpen] = useState(false) - - const lastContributor: FileContributor = contributors.length - ? contributors[0] - : ({ - avatar_url: "", - login: "", - html_url: "", - date: Date.now().toString(), - } as FileContributor) - const modalSize = useBreakpointValue({ base: "xl", md: "md" } as const) return ( @@ -92,35 +125,33 @@ const FileContributors = ({
- - - - -

- :{" "} - - @{lastContributor.login} - - , {lastEditLocaleTimestamp} + + +

+ {lastEditLocaleTimestamp}

+ + + + + +
- - - -
) diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 21ed09174c6..0c204a3f7bd 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -316,9 +316,9 @@ const Footer = ({ lastDeployLocaleTimestamp }: FooterProps) => {
diff --git a/src/components/LanguagePicker/MenuItem.tsx b/src/components/LanguagePicker/MenuItem.tsx index 752e4df006b..b7bfbba209b 100644 --- a/src/components/LanguagePicker/MenuItem.tsx +++ b/src/components/LanguagePicker/MenuItem.tsx @@ -73,7 +73,10 @@ const MenuItem = ({ displayInfo, ...props }: ItemProps) => {

{sourceName}

{isCurrent && ( - + )}

diff --git a/src/components/Nav/useNav.ts b/src/components/Nav/useNav.ts index f6384e8918a..12f34d351eb 100644 --- a/src/components/Nav/useNav.ts +++ b/src/components/Nav/useNav.ts @@ -234,6 +234,11 @@ export const useNav = () => { description: t("nav-prediction-markets-description"), href: "/prediction-markets/", }, + { + label: t("real-world-assets"), + description: t("nav-rwa-description"), + href: "/real-world-assets/", + }, ], }, ], diff --git a/src/components/Resources/types.ts b/src/components/Resources/types.ts index f9f271c8758..55044e16721 100644 --- a/src/components/Resources/types.ts +++ b/src/components/Resources/types.ts @@ -5,6 +5,7 @@ export type Item = { description: string href: string imgSrc: StaticImageData + className?: string } export type DashboardBox = { diff --git a/src/components/Resources/useResources.tsx b/src/components/Resources/useResources.tsx index 0ad4218477a..7d01afd6656 100644 --- a/src/components/Resources/useResources.tsx +++ b/src/components/Resources/useResources.tsx @@ -21,34 +21,38 @@ import IconBeaconchain from "@/public/images/resources/beaconcha-in.png" import IconBlobsGuru from "@/public/images/resources/blobsguru.png" import IconBlocknative from "@/public/images/resources/blocknative.png" import IconBlockscout from "@/public/images/resources/blockscout.webp" +import IconBundleBear from "@/public/images/resources/bundlebear.png" import IconCryptwerk from "@/public/images/resources/cryptowerk.png" import IconDefiLlama from "@/public/images/resources/defi-llama.png" -import IconDefiMarketCap from "@/public/images/resources/defi-market-cap.png" import IconDefiScan from "@/public/images/resources/defi-scan.png" import IconEas from "@/public/images/resources/eas.png" import IconEigenphi from "@/public/images/resources/eigenphi.png" import IconEthGlyphBlack from "@/public/images/resources/eth-glyph-black.png" import IconEthGlyphBlueCircle from "@/public/images/resources/eth-glyph-blue-circle.png" import IconEthGlyphEOrg from "@/public/images/resources/eth-glyph-e-org.png" -import IconEthGlyphRainbowFrame from "@/public/images/resources/eth-glyph-rainbow.frame.png" +import IconEthGlyphRainbowFrame from "@/public/images/resources/eth-glyph-rainbow-frame.png" import IconEtherealize from "@/public/images/resources/etherealize.png" import IconEtherscan from "@/public/images/resources/etherscan.png" import IconEthproofs from "@/public/images/resources/ethproofs.png" -import IconEthstaker from "@/public/images/resources/ethstaker.png" import IconFarcaster from "@/public/images/resources/farcaster.png" import IconGasTracker from "@/public/images/resources/gas.png" import IconGrowthepie from "@/public/images/resources/growthepie.png" import IconL2beat from "@/public/images/resources/l2beat.png" +import IconLuckyStaker from "@/public/images/resources/lucky-staker.png" import IconNftgo from "@/public/images/resources/nftgo.png" import IconNodewatch from "@/public/images/resources/nodewatch.png" +import IconPandaOps from "@/public/images/resources/panda.png" +import IconPectrified from "@/public/images/resources/pectrified.png" import IconRatedNetwork from "@/public/images/resources/rated-network.png" import IconRelayscan from "@/public/images/resources/relayscan.png" +import IconReserves from "@/public/images/resources/reserves.png" import IconRwa from "@/public/images/resources/rwa.png" import IconStablecoinsWtf from "@/public/images/resources/stablecoins-wtf.png" import IconSupermajority from "@/public/images/resources/supermajority.png" import IconTxCity from "@/public/images/resources/txcity.png" import IconUltrasoundMoney from "@/public/images/resources/ultrasound-money.png" import IconVisaOnchainAnalytics from "@/public/images/resources/visa-onchain-analytcs.png" +import IconWalletBeat from "@/public/images/resources/walletbeat.png" const formatSmallUSD = (value: number, locale: string): string => new Intl.NumberFormat(locale, { @@ -158,6 +162,14 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { href: "https://beaconcha.in", imgSrc: IconBeaconchain, }, + { + title: "Beacon Block Production Live", + description: t( + "page-resources-block-explorers-panda-ops-description" + ), + href: "https://lab.ethpandaops.io/beacon/block-production/live", + imgSrc: IconPandaOps, + }, { title: "Txcity.io", description: t("page-resources-block-explorers-txcity-description"), @@ -239,12 +251,6 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { href: "https://defillama.com", imgSrc: IconDefiLlama, }, - { - title: "DeFi Market Cap", - description: t("page-resources-defi-defimarketcap-description"), - href: "https://defimarketcap.io", - imgSrc: IconDefiMarketCap, - }, { title: "EigenPhi", description: t("page-resources-defi-eigenphi-description"), @@ -282,6 +288,7 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { description: t("page-resources-stablecoins-rwa-description"), href: "https://app.rwa.xyz/stablecoins", imgSrc: IconRwa, + className: "dark:[&_img]:invert", }, ], }, @@ -343,6 +350,30 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { href: "https://cryptwerk.com/analytics/ethereum/", imgSrc: IconCryptwerk, }, + { + title: "Strategic Ethereum Reserve", + description: t("page-resources-reserves-cryptowerk-description"), + href: "https://www.strategicethreserve.xyz", + imgSrc: IconReserves, + }, + ], + }, + { + title: t("page-resources-wallets-title"), + items: [ + { + title: "Wallet Beat", + description: t("page-resources-wallets-wallet-beat-description"), + href: "https://wallet.page/", + imgSrc: IconWalletBeat, + }, + { + title: "BundleBear", + description: t("page-resources-wallets-bundlebear-description"), + href: "https://www.bundlebear.com", + className: "dark:[&_img]:invert", + imgSrc: IconBundleBear, + }, ], }, ] @@ -404,10 +435,17 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { imgSrc: IconEtherscan, }, { - title: "luckystaker.com", + title: "LuckyStaker", description: t("page-resources-nodes-luckystaker-description"), href: "https://luckystaker.com", - imgSrc: IconEthstaker, + imgSrc: IconLuckyStaker, + className: "dark:[&_img]:invert", + }, + { + title: "Validators Overview after Pectra", + description: t("page-resources-nodes-pectrified-description"), + href: "https://pectrified.com/mainnet", + imgSrc: IconPectrified, }, { title: "Ethereum Validator Queue", @@ -483,6 +521,7 @@ export const useResources = ({ txCostsMedianUsd }): DashboardSection[] => { description: t("page-resources-relays-ratednetwork-description"), href: "https://explorer.rated.network/relays?network=mainnet", imgSrc: IconRatedNetwork, + className: "dark:[&_img]:invert", }, { title: "Relay Scan", diff --git a/src/components/TableOfContents/TableOfContentsLink.tsx b/src/components/TableOfContents/TableOfContentsLink.tsx index 9b6bbc4b004..f99c116348f 100644 --- a/src/components/TableOfContents/TableOfContentsLink.tsx +++ b/src/components/TableOfContents/TableOfContentsLink.tsx @@ -21,8 +21,8 @@ const Link = ({

( ref ) => { const handleOnClick = (e: React.MouseEvent) => { - toId && scrollIntoView(toId) + toId && scrollIntoView("#" + toId) customEventOptions && trackCustomEvent(customEventOptions) onClick?.(e) diff --git a/src/data/chains.ts b/src/data/chains.ts index a473a76b182..adeccf63f58 100644 --- a/src/data/chains.ts +++ b/src/data/chains.ts @@ -320,11 +320,11 @@ const chains = [ }, { name: "Blast Mainnet", - infoURL: "https://docs.blastblockchain.com", + infoURL: "https://docs.blastchain.org", chainId: 238, nativeCurrency: { - name: "Ether", - symbol: "ETH", + name: "One World Chain", + symbol: "OWCT", decimals: 18, }, chain: "ETH", @@ -1892,6 +1892,17 @@ const chains = [ }, chain: "DUSTBOY", }, + { + name: "Ethereum Hoodi", + infoURL: "https://hoodi.ethpandaops.io", + chainId: 560048, + nativeCurrency: { + name: "Hoodi Ether", + symbol: "ETH", + decimals: 18, + }, + chain: "ETH", + }, { name: "Zether Mainnet", infoURL: "https://zether.org", diff --git a/src/data/community-events.json b/src/data/community-events.json index fb3b4c78cf8..f01854403b6 100644 --- a/src/data/community-events.json +++ b/src/data/community-events.json @@ -763,5 +763,32 @@ "location": "Buenos Aires, ARG", "description": "A collaborative Ethereum week, built by and for everyone.", "imageUrl": "https://devconnect.org/og-argentina.png?reset=1" + }, + { + "title": "Builders Week Istanbul", + "startDate": "2025-09-01", + "endDate": "2025-09-07", + "href": "https://buildersweekistanbul.com/", + "location": "Istanbul, TR", + "description": "BWI is a week-long series of permissionless events designed to scale the blockchain development ecosystem.", + "imageUrl": "https://buildersweekistanbul.com/img/meta/meta.webp" + }, + { + "title": "Cryptist", + "startDate": "2025-09-01", + "endDate": "2025-09-01", + "href": "https://cryptist.org/", + "location": "Istanbul, TR", + "description": "Cryptist is an annual, day-long, zero-knowledge community event in Istanbul to learn, connect, and share amongst the Turkish ZK community.", + "imageUrl": "https://cryptist.org/img/meta.webp" + }, + { + "title": "EthIstanbul", + "startDate": "2025-09-05", + "endDate": "2025-09-07", + "href": "https://ethistanbul.io/", + "location": "Istanbul, TR", + "description": "ETHIstanbul is a conference and hackathon connecting you with global talents, industry professionals, and web3 companies advancing technology.", + "imageUrl": "https://ethistanbul.io/_next/image?url=%2F_next%2Fstatic%2Fmedia%2Fistanbul-background.99d6ad2a.webp&w=3840&q=75" } -] \ No newline at end of file +] diff --git a/src/data/crowdin/file-ids.json b/src/data/crowdin/file-ids.json index 89180181262..3e8114886ff 100644 --- a/src/data/crowdin/file-ids.json +++ b/src/data/crowdin/file-ids.json @@ -9,7 +9,7 @@ }, { "id": 6161, - "path": "/events/index.md" + "path": "/community/events/index.md" }, { "id": 6428, @@ -17,7 +17,7 @@ }, { "id": 7525, - "path": "/how-to-create-an-ethereum-account/index.md" + "path": "/guides/how-to-create-an-ethereum-account/index.md" }, { "id": 2716, @@ -241,11 +241,11 @@ }, { "id": 7591, - "path": "/withdrawals/index.md" + "path": "/staking/withdrawals/index.md" }, { "id": 8019, - "path": "/code-of-conduct/index.md" + "path": "/community/code-of-conduct/index.md" }, { "id": 8035, @@ -277,7 +277,7 @@ }, { "id": 6167, - "path": "/support/index.md" + "path": "/community/support/index.md" }, { "id": 7515, @@ -285,15 +285,15 @@ }, { "id": 7713, - "path": "/account-abstraction/index.md" + "path": "/roadmap/account-abstraction/index.md" }, { "id": 7717, - "path": "/danksharding/index.md" + "path": "/roadmap/danksharding/index.md" }, { "id": 7745, - "path": "/statelessness/index.md" + "path": "/roadmap/statelessness/index.md" }, { "id": 5553, @@ -379,6 +379,14 @@ "id": 7465, "path": "/zero-knowledge-proofs/index.md" }, + { + "id": 12582, + "path": "/prediction-markets/index.md" + }, + { + "id": 12584, + "path": "/ai-agents/index.md" + }, { "id": 2962, "path": "/foundation/index.md" @@ -417,7 +425,7 @@ }, { "id": 2946, - "path": "/beacon-chain/index.md" + "path": "/roadmap/beacon-chain/index.md" }, { "id": 2950, @@ -429,23 +437,23 @@ }, { "id": 7721, - "path": "/future-proofing/index.md" + "path": "/roadmap/future-proofing/index.md" }, { "id": 7729, - "path": "/scaling/index.md" + "path": "/roadmap/scaling/index.md" }, { "id": 7737, - "path": "/security/index.md" + "path": "/roadmap/security/index.md" }, { "id": 7749, - "path": "/user-experience/index.md" + "path": "/roadmap/user-experience/index.md" }, { "id": 7919, - "path": "/dvt/index.md" + "path": "/staking/dvt/index.md" }, { "id": 2776, diff --git a/src/data/externalTutorials.json b/src/data/externalTutorials.json index f816cd212dc..9ea358d99fb 100644 --- a/src/data/externalTutorials.json +++ b/src/data/externalTutorials.json @@ -247,7 +247,7 @@ { "url": "https://www.youtube.com/watch?v=AhJtmUqhAqg", "title": "How to build an on-chain DAO", - "description": "Using Compound and Openzeppelin as a basis, we build a 100% on-chain DAO using an ERC20 governance token for votes.", + "description": "Using Compound and Openzeppelin as a basis, we build a 100% onchain DAO using an ERC20 governance token for votes.", "author": "Patrick Collins", "authorGithub": "https://github.com/PatrickAlphaC", "tags": ["solidity", "typescript", "hardhat", "defi", "dao", "video"], @@ -259,7 +259,7 @@ { "url": "https://betterprogramming.pub/how-to-code-an-on-chain-dao-e525e13a57be", "title": "How to build an on-chain DAO", - "description": "Using Compound and Openzeppelin as a basis, we build a 100% on-chain DAO using an ERC20 governance token for votes.", + "description": "Using Compound and Openzeppelin as a basis, we build a 100% onchain DAO using an ERC20 governance token for votes.", "author": "Patrick Collins", "authorGithub": "https://github.com/PatrickAlphaC", "tags": ["solidity", "typescript", "hardhat", "defi", "dao"], @@ -334,7 +334,7 @@ { "url": "https://www.youtube.com/watch?v=9oERTH9Bkw0", "title": "How to make NFT Art with On-Chain Metadata", - "description": "Explore the world of using SVGs to generate random NFT ImageURIs and Metadata 100% on-chain.", + "description": "Explore the world of using SVGs to generate random NFT ImageURIs and Metadata 100% onchain.", "author": "Patrick Collins", "authorGithub": "https://github.com/PatrickAlphaC", "tags": [ diff --git a/src/data/placeholders/content-developers-tutorials-the-graph-fixing-web3-data-querying-data.json b/src/data/placeholders/content-developers-tutorials-the-graph-fixing-web3-data-querying-data.json index ff0f3f30217..2b13c1cecef 100644 --- a/src/data/placeholders/content-developers-tutorials-the-graph-fixing-web3-data-querying-data.json +++ b/src/data/placeholders/content-developers-tutorials-the-graph-fixing-web3-data-querying-data.json @@ -22,5 +22,9 @@ "/content/developers/tutorials/the-graph-fixing-web3-data-querying/thegraph-explorer.png": { "hash": "48f6c069", "base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAIAAAD5gJpuAAAACXBIWXMAAAPoAAAD6AG1e1JrAAACJklEQVR4nAEbAuT9ABYRIRYQIhMPHRYTJA8RJwcIHwMCGAgGGwQDFwIBFAAAEwUEGAUEGAAAFAkIGw4NHwASM2EXOWwXN3ATLFxFPUZYUFNYUFNAOUNLSlpjX21oXnFJR1ZKQ1ZeWGlgVmhKQVQAP3fcSYf3S4b+M2O8rZp9u6aCvKeEno17087V7//31frsyMTIx667/8rg/9Hjxa25ADNmuD96yjx6zSdVmYl6ZX9yWG1fR3NoW6uor3G/o0G1jJWjpKqKn9KIp9Rckad8lAA2W4pCZpQpWIoXP2lhV055bl1cUT5JPDh6goVZn4ZooIt3fH6EX3WthpWhZH14UGgAGB1IKy5YJCpTEBc9LCwuLzczLTozKiovQTxJTE5RQkRINzFASkRPYFBZVEtOQjhGACd8Vi6LXDCPXR5lT0ooPZlDSpMmR0cbQYKBk6edqqWbqId/kEpBXjEiQTQlQ1NFYABT3nJh/Xpl/4BBt2nGXUr/eVf/glG8UlS9ub1zb2h9e3O/u76jlKZkn5JymY+llKUAO6ldQLRhQrddLYpQqnx2u2KByGdIqlVUTUlTFxcWGBgVUUxUQ0JWM5RyMHxcNi1CAC1+UESWYSl/PhlaOH9xbZ51cY1WZmIlNmRjaD88OSQjH09ITy4tPTpdUR08MBURIAARPi0ZSzQSRy8IListGSFJKiVFJicoFSVCQExHQUZIRUhCPEkaEiUeKy0aICUaECI8o7+y+wRmBAAAAABJRU5ErkJggg==" + }, + "/content/developers/tutorials/the-graph-fixing-web3-data-querying/graphql-query.gif": { + "hash": "15d03461", + "base64": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAMCAYAAABr5z2BAAAACXBIWXMAAAPoAAAD6AG1e1JrAAABAklEQVR4nGMwM7f4LyUl9V9SknSsoKj0n8HG2vZ/c3Pz/3nz5v6fOnUK0XjevNn/i4qK/jNMmTL1f2Zm5n8PD4//pIKjR4/9Z+jt7fs/f/78/wkJCf+/fv1KNAaBfXv3/WfIysr6/+DBfZJtR3EBCMBMJdmAoqLi/9evX/t/9uyZ/8ePHft/5MgRovC5c+f+z5wx8z9DT0/P/6XLV4Hx9evX/7979+7/z+8//3/79u3/j+/fCbtg/vwF/7fv3PNfTc/0/9p16/9Pau3531xW+z/Yxfd/aXr+/xfPnhP2wuvXr+GCINvRQxuvAdOmTSct5NANaGlpAwbIBaIDDzkQQd4HAMXFfREF8kXGAAAAAElFTkSuQmCC" } } \ No newline at end of file diff --git a/src/data/published.json b/src/data/published.json index 989c2d3f9a9..7adb21eaa5a 100644 --- a/src/data/published.json +++ b/src/data/published.json @@ -1 +1 @@ -{"date":"2025-05-14"} +{"date":"2025-05-28"} diff --git a/src/data/roadmap/releases.tsx b/src/data/roadmap/releases.tsx index 095734ed0c4..ca06fd300d1 100644 --- a/src/data/roadmap/releases.tsx +++ b/src/data/roadmap/releases.tsx @@ -1,12 +1,11 @@ import { StaticImageData } from "next/image" -import CommunityHeroImage from "@/public/images/heroes/community-hero.png" import DevelopersHubHeroImage from "@/public/images/heroes/developers-hub-hero.jpg" import GuidesHubHeroImage from "@/public/images/heroes/guides-hub-hero.jpg" import Layer2HubHeroImage from "@/public/images/heroes/layer-2-hub-hero.jpg" import QuizzesHubHeroImage from "@/public/images/heroes/quizzes-hub-hero.png" +import FusakaImage from "@/public/images/roadmap/roadmap-fusaka.png" import PectraImage from "@/public/images/roadmap/roadmap-pectra.png" - interface Release { image: StaticImageData releaseName: string @@ -143,9 +142,9 @@ export const releasesData: Release[] = [ href: "/roadmap/pectra", }, { - image: CommunityHeroImage, + image: FusakaImage, releaseName: "Fusaka", - releaseDate: "2026", + releaseDate: "2025", content: (

@@ -160,10 +159,8 @@ export const releasesData: Release[] = [

Potential Additional Features

    -
  • - EIP-7688: Enhanced smart contract access to network information -
  • -
  • Blob fee market improvementse
  • +
  • Support for secure enclaves on mobile devices to improve UX
  • +
  • Blob fee market improvements
  • Further improvements to validator efficiency and network performance
  • diff --git a/src/data/translationProgress.json b/src/data/translationProgress.json index 093d910ac24..78263040575 100644 --- a/src/data/translationProgress.json +++ b/src/data/translationProgress.json @@ -3,686 +3,686 @@ "languageId": "af", "words": { "approved": 1554, - "total": 281165 + "total": 280914 } }, { "languageId": "am", "words": { - "approved": 10713, - "total": 281165 + "approved": 10702, + "total": 280914 } }, { "languageId": "ar", "words": { - "approved": 37264, - "total": 281165 + "approved": 37042, + "total": 280914 } }, { "languageId": "az", "words": { - "approved": 22282, - "total": 281165 + "approved": 22042, + "total": 280914 } }, { "languageId": "be", "words": { - "approved": 35870, - "total": 281165 + "approved": 35837, + "total": 280914 } }, { "languageId": "bg", "words": { - "approved": 18855, - "total": 281165 + "approved": 18799, + "total": 280914 } }, { "languageId": "bi", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "bn", "words": { - "approved": 29381, - "total": 281165 + "approved": 29161, + "total": 280914 } }, { "languageId": "br-FR", "words": { "approved": 43, - "total": 281165 + "total": 280914 } }, { "languageId": "bs", "words": { - "approved": 7431, - "total": 281165 + "approved": 7428, + "total": 280914 } }, { "languageId": "ca", "words": { - "approved": 27112, - "total": 281165 + "approved": 26935, + "total": 280914 } }, { "languageId": "cs", "words": { - "approved": 140321, - "total": 281165 + "approved": 136936, + "total": 280914 } }, { "languageId": "da", "words": { - "approved": 2485, - "total": 281165 + "approved": 2482, + "total": 280914 } }, { "languageId": "de", "words": { - "approved": 201812, - "total": 281165 + "approved": 198291, + "total": 280914 } }, { "languageId": "dv", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "ee", "words": { - "approved": 2505, - "total": 281165 + "approved": 2502, + "total": 280914 } }, { "languageId": "el", "words": { - "approved": 269545, - "total": 281165 + "approved": 266720, + "total": 280914 } }, { "languageId": "eo", "words": { "approved": 46, - "total": 281165 + "total": 280914 } }, { "languageId": "es-EM", "words": { - "approved": 258776, - "total": 281165 + "approved": 254050, + "total": 280914 } }, { "languageId": "et", "words": { "approved": 19, - "total": 281165 + "total": 280914 } }, { "languageId": "eu", "words": { "approved": 10, - "total": 281165 + "total": 280914 } }, { "languageId": "fa", "words": { - "approved": 246929, - "total": 281165 + "approved": 242192, + "total": 280914 } }, { "languageId": "fa-AF", "words": { "approved": 64, - "total": 281165 + "total": 280914 } }, { "languageId": "fi", "words": { - "approved": 19035, - "total": 281165 + "approved": 18889, + "total": 280914 } }, { "languageId": "fil", "words": { - "approved": 52867, - "total": 281165 + "approved": 52518, + "total": 280914 } }, { "languageId": "fr", "words": { - "approved": 268454, - "total": 281165 + "approved": 263694, + "total": 280914 } }, { "languageId": "ga-IE", "words": { - "approved": 179294, - "total": 281227 + "approved": 175704, + "total": 280976 } }, { "languageId": "gi", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "gl", "words": { - "approved": 2487, - "total": 281165 + "approved": 2484, + "total": 280914 } }, { "languageId": "gu-IN", "words": { - "approved": 2599, - "total": 281165 + "approved": 2596, + "total": 280914 } }, { "languageId": "ha", "words": { - "approved": 41923, - "total": 281165 + "approved": 40675, + "total": 280914 } }, { "languageId": "he", "words": { - "approved": 3203, - "total": 281165 + "approved": 3200, + "total": 280914 } }, { "languageId": "hi", "words": { - "approved": 170485, - "total": 281165 + "approved": 165902, + "total": 280914 } }, { "languageId": "hr", "words": { - "approved": 20491, - "total": 281165 + "approved": 20415, + "total": 280914 } }, { "languageId": "hu", "words": { - "approved": 261878, - "total": 281165 + "approved": 257130, + "total": 280914 } }, { "languageId": "hy-AM", "words": { - "approved": 9478, - "total": 281165 + "approved": 9467, + "total": 280914 } }, { "languageId": "id", "words": { - "approved": 131029, - "total": 281165 + "approved": 128250, + "total": 280914 } }, { "languageId": "ig", "words": { - "approved": 21811, - "total": 281165 + "approved": 21757, + "total": 280914 } }, { "languageId": "it", "words": { - "approved": 265322, - "total": 281165 + "approved": 260583, + "total": 280914 } }, { "languageId": "ja", "words": { - "approved": 251244, - "total": 281165 + "approved": 246510, + "total": 280914 } }, { "languageId": "ka", "words": { - "approved": 5302, - "total": 281165 + "approved": 5299, + "total": 280914 } }, { "languageId": "kk", "words": { - "approved": 10260, - "total": 281165 + "approved": 10255, + "total": 280914 } }, { "languageId": "km", "words": { - "approved": 11925, - "total": 281165 + "approved": 11871, + "total": 280914 } }, { "languageId": "kn", "words": { - "approved": 42418, - "total": 281165 + "approved": 42087, + "total": 280914 } }, { "languageId": "ko", "words": { - "approved": 51916, - "total": 281165 + "approved": 51668, + "total": 280914 } }, { "languageId": "ku", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "ky", "words": { "approved": 74, - "total": 281165 + "total": 280914 } }, { "languageId": "lb", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "lt", "words": { - "approved": 2972, - "total": 281165 + "approved": 2969, + "total": 280914 } }, { "languageId": "lv", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "mai", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "mk", "words": { "approved": 54, - "total": 281165 + "total": 280914 } }, { "languageId": "ml-IN", "words": { - "approved": 17972, - "total": 281165 + "approved": 17898, + "total": 280914 } }, { "languageId": "mn", "words": { "approved": 48, - "total": 281165 + "total": 280914 } }, { "languageId": "mr", "words": { - "approved": 21374, - "total": 281165 + "approved": 21154, + "total": 280914 } }, { "languageId": "ms", "words": { - "approved": 70512, - "total": 281165 + "approved": 70066, + "total": 280914 } }, { "languageId": "my", "words": { "approved": 65, - "total": 281165 + "total": 280914 } }, { "languageId": "ne-NP", "words": { - "approved": 2498, - "total": 281165 + "approved": 2495, + "total": 280914 } }, { "languageId": "nl", "words": { - "approved": 102935, - "total": 281165 + "approved": 99628, + "total": 280914 } }, { "languageId": "no", "words": { - "approved": 2950, - "total": 281165 + "approved": 2947, + "total": 280914 } }, { "languageId": "ny", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "or", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "pa-IN", "words": { "approved": 3, - "total": 281165 + "total": 280914 } }, { "languageId": "pcm", "words": { - "approved": 71416, - "total": 281165 + "approved": 71318, + "total": 280914 } }, { "languageId": "pl", "words": { - "approved": 110137, - "total": 281165 + "approved": 106822, + "total": 280914 } }, { "languageId": "ps", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "pt-BR", "words": { - "approved": 265836, - "total": 281165 + "approved": 261079, + "total": 280914 } }, { "languageId": "pt-PT", "words": { - "approved": 36951, - "total": 281165 + "approved": 36615, + "total": 280914 } }, { "languageId": "ro", "words": { - "approved": 43582, - "total": 281165 + "approved": 43428, + "total": 280914 } }, { "languageId": "ru", "words": { - "approved": 117296, - "total": 281165 + "approved": 112755, + "total": 280914 } }, { "languageId": "sat", "words": { "approved": 57, - "total": 281165 + "total": 280914 } }, { "languageId": "si-LK", "words": { "approved": 65, - "total": 281165 + "total": 280914 } }, { "languageId": "sk", "words": { - "approved": 45417, - "total": 281165 + "approved": 45108, + "total": 280914 } }, { "languageId": "sl", "words": { - "approved": 30526, - "total": 281165 + "approved": 30436, + "total": 280914 } }, { "languageId": "sn", "words": { - "approved": 6984, - "total": 281165 + "approved": 6979, + "total": 280914 } }, { "languageId": "so", "words": { "approved": 62, - "total": 281165 + "total": 280914 } }, { "languageId": "sq", "words": { "approved": 67, - "total": 281165 + "total": 280914 } }, { "languageId": "sr-CS", "words": { - "approved": 37505, - "total": 281165 + "approved": 37173, + "total": 280914 } }, { "languageId": "sv-SE", "words": { - "approved": 10101, - "total": 281165 + "approved": 10090, + "total": 280914 } }, { "languageId": "sw", "words": { - "approved": 24835, - "total": 281165 + "approved": 24651, + "total": 280914 } }, { "languageId": "ta", "words": { - "approved": 2764, - "total": 281165 + "approved": 2761, + "total": 280914 } }, { "languageId": "te", "words": { - "approved": 26795, - "total": 281165 + "approved": 24679, + "total": 280914 } }, { "languageId": "tg", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "th", "words": { - "approved": 7445, - "total": 281165 + "approved": 7427, + "total": 280914 } }, { "languageId": "ti", "words": { "approved": 0, - "total": 281165 + "total": 280914 } }, { "languageId": "tk", "words": { - "approved": 6163, - "total": 281165 + "approved": 6160, + "total": 280914 } }, { "languageId": "tl", "words": { - "approved": 49041, - "total": 281165 + "approved": 48689, + "total": 280914 } }, { "languageId": "tr", "words": { - "approved": 251906, - "total": 281165 + "approved": 247156, + "total": 280914 } }, { "languageId": "tw", "words": { - "approved": 5991, - "total": 281165 + "approved": 5988, + "total": 280914 } }, { "languageId": "uk", "words": { - "approved": 82392, - "total": 281165 + "approved": 79453, + "total": 280914 } }, { "languageId": "ur-IN", "words": { - "approved": 2478, - "total": 281165 + "approved": 2475, + "total": 280914 } }, { "languageId": "ur-PK", "words": { "approved": 865, - "total": 281165 + "total": 280914 } }, { "languageId": "uz", "words": { - "approved": 36683, - "total": 281165 + "approved": 36607, + "total": 280914 } }, { "languageId": "vi", "words": { - "approved": 34849, - "total": 281165 + "approved": 34590, + "total": 280914 } }, { "languageId": "yo", "words": { - "approved": 43667, - "total": 281165 + "approved": 43320, + "total": 280914 } }, { "languageId": "zh-CN", "words": { - "approved": 269456, - "total": 281165 + "approved": 264644, + "total": 280914 } }, { "languageId": "zh-TW", "words": { - "approved": 240791, - "total": 281165 + "approved": 236052, + "total": 280914 } }, { "languageId": "zu", "words": { "approved": 43, - "total": 281165 + "total": 280914 } } ] \ No newline at end of file diff --git a/src/intl/de/page-gas.json b/src/intl/de/page-gas.json index acaa56ef4dc..bcde05bec16 100644 --- a/src/intl/de/page-gas.json +++ b/src/intl/de/page-gas.json @@ -53,7 +53,7 @@ "page-gas-faq-header": "Häufig gestellte Fragen", "page-gas-faq-question-1-q": "Wer erhält die Spritgebühr in meiner Transaktion?", "page-gas-faq-question-1-a-1": "Der Hauptteil der Gasgebühr – die Basisgebühr – wird durch das Protokoll zerstört (verbrannt). Die Prioritätsgebühr, die ggf. in Ihrer Transaktion inbegriffen ist, wird dem Validator übergeben, der Ihre Transaktion vorgeschlagen hat.", - "page-gas-faq-question-1-a-2": "Eine detaillierte Beschreibung des Prozesses finden Sie in den Sprit-Entwicklerdokumenten.", + "page-gas-faq-question-1-a-2": "Eine detaillierte Beschreibung des Prozesses finden Sie in den Sprit-Entwicklerdokumenten.", "page-gas-faq-question-2-q": "Muss ich Sprit in ETH bezahlen?", "page-gas-faq-question-2-a-1": "Ja. Alle Spritgebühren auf Ethereum müssen in der nativen ETH-Währung bezahlt werden.", "page-gas-faq-question-2-a-2": "Mehr zu ETH", diff --git a/src/intl/en/common.json b/src/intl/en/common.json index 15618378555..d448b840958 100644 --- a/src/intl/en/common.json +++ b/src/intl/en/common.json @@ -31,7 +31,7 @@ "content-standardization": "Content standardization", "contributing": "Contributing", "contributors": "Contributors", - "contributors-thanks": "Everyone who has contributed to this page – thank you!", + "contributors-thanks": "Everyone who has contributed to this page – thank you!", "cookie-policy": "Cookie policy", "copied": "Copied", "copy": "Copy", @@ -199,7 +199,7 @@ "language-zh-tw": "Chinese Traditional", "languages": "Languages", "last-24-hrs": "Last 24 hours", - "last-edit": "Last edit", + "page-last-update": "Page last update:", "last-updated": "Last updated", "layer-2": "Layer 2", "learn": "Learn", @@ -320,6 +320,7 @@ "nav-roadmap-user-experience": "Better user experience", "nav-roadmap-ux-description": "Using Ethereum needs to be simplified", "nav-roadmap-ux-label": "Better user experience", + "nav-rwa-description": "A method for turning valuable commodities into digital tokens.", "nav-run-a-node-description": "Become fully sovereign while helping secure the network", "nav-security-description": "Learn best practices when using cryptocurrency", "nav-smart-contracts-description": "The fundamental building blocks of the Ethereum ecosystem", @@ -389,6 +390,7 @@ "rollup-component-technology-and-risk-summary": "Technology and risk summary", "rollup-component-website": "Website", "run-a-node": "Run a node", + "real-world-assets": "RWAs - Real-world assets", "saas": "Staking as a service", "scaling": "Scaling", "search": "Search", @@ -449,5 +451,6 @@ "withdrawals": "Staking withdrawals", "wrapped-ether": "Wrapped Ether", "yes": "Yes", - "zero-knowledge-proofs": "Zero-knowledge proofs" + "zero-knowledge-proofs": "Zero-knowledge proofs", + "translator": "Translator" } diff --git a/src/intl/en/page-resources.json b/src/intl/en/page-resources.json index d5b283ee0cf..83878bc0480 100644 --- a/src/intl/en/page-resources.json +++ b/src/intl/en/page-resources.json @@ -13,6 +13,7 @@ "page-resources-block-explorers-etherscan-description": "Etherscan is a block explorer and analytics platform for Ethereum, a decentralized smart contracts platform.", "page-resources-block-explorers-beaconchain-description": "Open source Ethereum explorer showing the Ethereum Mainnet 🚀.", "page-resources-block-explorers-txcity-description": "A funny visualizer of the Ethereum blocks in real-time.", + "page-resources-block-explorers-panda-ops-description": "Live dashboard for Ethereum beacon chain block production by ethPandaOps.", "page-resources-eth-asset-title": "ETH the asset", "page-resources-eth-asset-etherealize-description": "Ethereum is the largest, most secure, and most open blockchain for the world to use. And Ethereum is open for business.", "page-resources-eth-asset-ultrasound-description": "Ultra sound money is an Ethereum meme focusing on the likely decrease of the ETH supply.", @@ -25,7 +26,6 @@ "page-resources-gas-gasfees-description": "Gas costs data tracker for Ethereum networks.", "page-resources-defi-title": "DeFi", "page-resources-defi-defillama-description": "DefiLlama is the largest TVL aggregator for DeFi (Decentralized Finance).", - "page-resources-defi-defimarketcap-description": "Top 100 DeFi tokens by market capitalization.", "page-resources-defi-eigenphi-description": "Wanna understand DeFi transactions and trading strategies?", "page-resources-defi-defiscan-description": "Verifiable insights into the maturity and risks of DeFi.", "page-resources-stablecoins-title": "Stablecoins", @@ -40,8 +40,9 @@ "page-resources-applications-farcaster-description": "Data from Farcaster usage.", "page-resources-applications-dappradar-description": "Explore top blockchain dapps, NFTs, games, DeFi projects, tokens, and airdrops. Track rankings, explore market insights, find trending projects, and unlock rewards with the world’s dapp store.", "page-resources-adoption-title": "Ethereum Adoption", - "page-resources-adoption-ethereumadoption-description": "Ethereum Censorability Monitor.", + "page-resources-adoption-ethereumadoption-description": "A list of high profile entities building on Ethereum.", "page-resources-adoption-cryptowerk-description": "Ethereum adoption analytics based on Cryptwerk merchants database - map, countries, companies, businesses, categories, rating.", + "page-resources-adoption-reserves-description": "A dashboard for the Strategic Ethereum Reserve initiative.", "page-resources-roadmap-title": "Ethereum Roadmap", "page-resources-roadmap-ethroadmap-description": "Detailed visualization on Ethereum roadmap and the next network upgrade.", "page-resources-blobs-title": "Blobs", @@ -52,6 +53,7 @@ "page-resources-nodes-ethernodes-description": "Ethereum Mainnet statistics.", "page-resources-nodes-etherscan-description": "Daily.", "page-resources-nodes-luckystaker-description": "Daily proposal probability of getting a block.", + "page-resources-nodes-pectrified-description": "Ethereum Pectra fork statistics for validators.", "page-resources-nodes-validatorqueue-description": "A dashboard showing the Ethereum validator enter and exit queue and estimated wait times.", "page-resources-network-resilience-title": "Network resilience", "page-resources-network-resilience-neutralitywatch-description": "Ethereum Censorability Monitor.", @@ -59,7 +61,7 @@ "page-resources-network-resilience-clientdiversity-description": "Improve Ethereum's resilience by using a minority client.", "page-resources-network-resilience-supermajority-description": "The supermajority client risk of the Ethereum execution layer, especially the client usage of staking services.", "page-resources-attestations-title": "Attestations", - "page-resources-attestations-eas-description": "EAS enables anyone to create and validate on-chain and off-chain attestations on Ethereum.", + "page-resources-attestations-eas-description": "EAS enables anyone to create and validate onchain and off-chain attestations on Ethereum.", "page-resources-relays-title": "Relays", "page-resources-relays-beaconchain-description": "Validators can use relays to outsource their block production to entities specialized in extracting extra revenue.", "page-resources-relays-ratednetwork-description": "MEV relay market share, total value relayed, value per block, and other statistics for Ethereum network.", @@ -67,6 +69,9 @@ "page-resources-mev-title": "MEV", "page-resources-mev-mevboost-description": "The purpose of this website is to educate degens about stablecoins.", "page-resources-mev-mevwatch-description": "Some MEV-Boost relays are regulated under OFAC and will censor certain transactions. Use this tool to observe the effect it's having on Ethereum blocks.", + "page-resources-wallets-title": "Wallets", + "page-resources-wallets-wallet-beat-description": "A simple Ethereum wallet dashboard and summary tool.", + "page-resources-wallets-bundlebear-description": "Dashboards and analytics for ERC-4337 and EIP-7702 smart accounts.", "page-resources-zk-adoption-title": "ZK adoption", "page-resources-zk-adoption-ethproofs-description": "SNARKs that scale Ethereum.", "page-resources-zk-adoption-l2beat-description": "ZK Catalog by L2BEAT is a community-driven resource offering detailed insights into the ZK technology utilized by various blockchain projects.", @@ -85,4 +90,4 @@ "page-resources-whats-on-this-page": "What's on this page", "page-resources-banner-notification-message": "Resources dashboard is new!", "page-resources-share-feedback": "Please share your feedback with us" -} \ No newline at end of file +} diff --git a/src/intl/en/template-usecase.json b/src/intl/en/template-usecase.json index f55f20f9196..4e8dd8ba943 100644 --- a/src/intl/en/template-usecase.json +++ b/src/intl/en/template-usecase.json @@ -12,5 +12,6 @@ "template-usecase-dropdown": "Ethereum use cases", "template-usecase-banner": "Uses of Ethereum are always developing and evolving. Add any info you think will make things clearer or more up to date.", "template-usecase-edit-link": "Edit page", - "template-usecase-dropdown-aria": "Use case dropdown menu" + "template-usecase-dropdown-aria": "Use case dropdown menu", + "template-usecase-dropdown-rwa": "Real-world assets (RWAs)" } diff --git a/src/intl/es/common.json b/src/intl/es/common.json index 37b9c6df35f..96a7d12c16c 100644 --- a/src/intl/es/common.json +++ b/src/intl/es/common.json @@ -240,7 +240,7 @@ "nav-did-description": "Cree y sea propietario de sus identificadores descentralizados propios", "nav-docs-description": "Documentación de ayuda para entender y construir en Ethereum", "nav-docs-design-description": "Descripción de los retos de diseño únicos de Web3, mejores prácticas y hallazgos de investigaciones de los usuarios", - "nav-docs-design-label": "Lo esencial del diseño de la UX/IU", + "nav-docs-design-label": "Lo esencial del diseño de la UX/UI", "nav-docs-foundation-description": "Los fundamentos para desarrollar en Ethereum", "nav-docs-foundation-label": "Temas fundamentales", "nav-docs-overview-description": "El sitio donde buscar documentación para desarrolladores", diff --git a/src/layouts/ContentLayout.tsx b/src/layouts/ContentLayout.tsx index 230d5d5bcf0..6ec2409e940 100644 --- a/src/layouts/ContentLayout.tsx +++ b/src/layouts/ContentLayout.tsx @@ -1,6 +1,9 @@ import type { HTMLAttributes } from "react" +import { FileContributor } from "@/lib/types" + import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import LeftNavBar, { LeftNavBarProps } from "@/components/LeftNavBar" import { ContentContainer, Page } from "@/components/MdComponents" import MobileButtonDropdown from "@/components/MobileButtonDropdown" @@ -9,6 +12,8 @@ type ContentLayoutProps = HTMLAttributes & Pick & { children: React.ReactNode heroSection: React.ReactNode + contributors: FileContributor[] + lastEditLocaleTimestamp: string } export const ContentLayout = ({ @@ -17,6 +22,8 @@ export const ContentLayout = ({ tocItems, maxDepth, heroSection, + contributors, + lastEditLocaleTimestamp, ...props }: ContentLayoutProps) => { return ( @@ -33,9 +40,14 @@ export const ContentLayout = ({ {children} + + - {dropdownLinks && }
diff --git a/src/layouts/Docs.tsx b/src/layouts/Docs.tsx index 88d8609556e..10ec8387c92 100644 --- a/src/layouts/Docs.tsx +++ b/src/layouts/Docs.tsx @@ -1,3 +1,4 @@ +import { MDXRemoteProps } from "next-mdx-remote" import type { HTMLAttributes } from "react" import { ChildOnlyProp } from "@/lib/types" @@ -34,8 +35,7 @@ import YouTube from "@/components/YouTube" import { cn } from "@/lib/utils/cn" import { getEditPath } from "@/lib/utils/editPath" -const baseHeadingClasses = - "font-mono uppercase font-bold scroll-mt-40 break-words" +const baseHeadingClasses = "font-bold scroll-mt-40 break-words" const H1 = (props: HTMLAttributes) => ( ) => ( const BackToTop = (props: ChildOnlyProp) => (
- ↑ +
) +const Pre = (props: React.HTMLAttributes) => { + const match = props.className?.match(/(language-\S+)/) + const codeLanguage = match ? match[0] : "plain-text" + return +} + export const docsComponents = { h1: H1, h2: H2, h3: H3, h4: H4, - pre: Codeblock, + pre: Pre, ...mdxTableComponents, ButtonLink, Card, @@ -88,7 +94,7 @@ export const docsComponents = { GlossaryTooltip, InfoBanner, YouTube, -} +} as MDXRemoteProps["components"] type DocsLayoutProps = Pick< MdPageContent, diff --git a/src/layouts/Static.tsx b/src/layouts/Static.tsx index 5d0159b8814..0bd2a331ac0 100644 --- a/src/layouts/Static.tsx +++ b/src/layouts/Static.tsx @@ -10,6 +10,7 @@ import Callout from "@/components/Callout" import Contributors from "@/components/Contributors" import EnergyConsumptionChart from "@/components/EnergyConsumptionChart" import FeedbackCard from "@/components/FeedbackCard" +import FileContributors from "@/components/FileContributors" import GlossaryDefinition from "@/components/Glossary/GlossaryDefinition" import GlossaryTooltip from "@/components/Glossary/GlossaryTooltip" import { HubHero } from "@/components/Hero" @@ -78,7 +79,11 @@ export const staticComponents = { type StaticLayoutProps = ChildOnlyProp & Pick< MdPageContent, - "slug" | "tocItems" | "lastEditLocaleTimestamp" | "contentNotTranslated" + | "slug" + | "tocItems" + | "lastEditLocaleTimestamp" + | "contentNotTranslated" + | "contributors" > & { frontmatter: StaticFrontmatter } @@ -89,6 +94,7 @@ export const StaticLayout = ({ tocItems, lastEditLocaleTimestamp, contentNotTranslated, + contributors, }: StaticLayoutProps) => { const locale = useLocale() @@ -135,6 +141,11 @@ export const StaticLayout = ({ /> {children} +
diff --git a/src/layouts/Tutorial.tsx b/src/layouts/Tutorial.tsx index 69dde183913..7cb4e7e9147 100644 --- a/src/layouts/Tutorial.tsx +++ b/src/layouts/Tutorial.tsx @@ -1,3 +1,4 @@ +import { MDXRemoteProps } from "next-mdx-remote" import type { HTMLAttributes } from "react" import type { ChildOnlyProp } from "@/lib/types" @@ -28,17 +29,11 @@ import YouTube from "@/components/YouTube" import { getEditPath } from "@/lib/utils/editPath" const Heading1 = (props: HTMLAttributes) => ( - + ) const Heading2 = (props: HTMLAttributes) => ( - + ) const Heading3 = (props: HTMLAttributes) => ( @@ -66,6 +61,12 @@ const KBD = (props: HTMLAttributes) => ( /> ) +const Pre = (props: React.HTMLAttributes) => { + const match = props.className?.match(/(language-\S+)/) + const codeLanguage = match ? match[0] : "plain-text" + return +} + export const tutorialsComponents = { a: TooltipLink, h1: Heading1, @@ -74,7 +75,7 @@ export const tutorialsComponents = { h4: Heading4, p: Paragraph, kbd: KBD, - pre: Codeblock, + pre: Pre, ...mdxTableComponents, ButtonLink, CallToContribute, @@ -83,7 +84,8 @@ export const tutorialsComponents = { EnvWarningBanner, InfoBanner, YouTube, -} +} as MDXRemoteProps["components"] + type TutorialLayoutProps = ChildOnlyProp & Pick< MdPageContent, @@ -123,6 +125,7 @@ export const TutorialLayout = ({ /> {children} diff --git a/src/layouts/index.ts b/src/layouts/index.ts index 58315d9cc7d..ed4ab2d45c7 100644 --- a/src/layouts/index.ts +++ b/src/layouts/index.ts @@ -1,3 +1,7 @@ +import { MDXRemoteProps } from "next-mdx-remote" + +import { Layout } from "@/lib/types" + import { docsComponents, DocsLayout } from "./Docs" import * as mdLayouts from "./md" import { staticComponents, StaticLayout } from "./Static" @@ -20,13 +24,13 @@ export const layoutMapping = { tutorial: TutorialLayout, } -export const componentsMapping = { - ...staticComponents, - ...mdLayouts.useCasesComponents, - ...mdLayouts.stakingComponents, - ...mdLayouts.roadmapComponents, - ...mdLayouts.upgradeComponents, - ...mdLayouts.translatathonComponents, - ...docsComponents, - ...tutorialsComponents, -} as const +export const componentsMapping: Record = { + static: staticComponents, + "use-cases": mdLayouts.useCasesComponents, + staking: mdLayouts.stakingComponents, + roadmap: mdLayouts.roadmapComponents, + upgrade: mdLayouts.upgradeComponents, + translatathon: mdLayouts.translatathonComponents, + docs: docsComponents, + tutorial: tutorialsComponents, +} diff --git a/src/layouts/md/Roadmap.tsx b/src/layouts/md/Roadmap.tsx index 9a38cdcf7ed..fac90dcac5b 100644 --- a/src/layouts/md/Roadmap.tsx +++ b/src/layouts/md/Roadmap.tsx @@ -11,19 +11,21 @@ import { ContentLayout } from "../ContentLayout" import { useTranslation } from "@/hooks/useTranslation" import RoadmapHubHeroImage from "@/public/images/heroes/roadmap-hub-hero.jpg" -const CardGrid = (props: ChildOnlyProp) => ( -
-) - // Roadmap layout components export const roadmapComponents = { - CardGrid, RoadmapActionCard, RoadmapImageContent, } type RoadmapLayoutProps = ChildOnlyProp & - Pick & { + Pick< + MdPageContent, + | "slug" + | "tocItems" + | "contentNotTranslated" + | "contributors" + | "lastEditLocaleTimestamp" + > & { frontmatter: RoadmapFrontmatter } export const RoadmapLayout = ({ @@ -31,6 +33,8 @@ export const RoadmapLayout = ({ frontmatter, slug, tocItems, + contributors, + lastEditLocaleTimestamp, contentNotTranslated, }: RoadmapLayoutProps) => { const { t } = useTranslation("common") @@ -99,6 +103,8 @@ export const RoadmapLayout = ({ tocItems={tocItems} dropdownLinks={dropdownLinks} maxDepth={frontmatter.sidebarDepth} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={ slug === "/roadmap/" ? ( & { + Pick< + MdPageContent, + | "slug" + | "tocItems" + | "contentNotTranslated" + | "contributors" + | "lastEditLocaleTimestamp" + > & { frontmatter: StakingFrontmatter } @@ -84,6 +91,8 @@ export const StakingLayout = ({ slug, tocItems, contentNotTranslated, + contributors, + lastEditLocaleTimestamp, }: StakingLayoutProps) => { const { t } = useTranslation("page-staking") @@ -164,6 +173,8 @@ export const StakingLayout = ({ tocItems={tocItems} dropdownLinks={dropdownLinks} maxDepth={frontmatter.sidebarDepth} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={} > {children} diff --git a/src/layouts/md/Translatathon.tsx b/src/layouts/md/Translatathon.tsx index 24b6ba50eac..1927b16a957 100644 --- a/src/layouts/md/Translatathon.tsx +++ b/src/layouts/md/Translatathon.tsx @@ -100,7 +100,10 @@ export const translatathonComponents = { } type TranslatathonLayoutProps = ChildOnlyProp & - Pick & { + Pick< + MdPageContent, + "slug" | "tocItems" | "contributors" | "lastEditLocaleTimestamp" + > & { frontmatter: SharedFrontmatter } @@ -109,6 +112,8 @@ export const TranslatathonLayout = ({ frontmatter, slug, tocItems, + contributors, + lastEditLocaleTimestamp, }: TranslatathonLayoutProps) => { const dropdownLinks: ButtonDropdownList = { text: "Translatathon menu", @@ -181,6 +186,8 @@ export const TranslatathonLayout = ({ dir="ltr" tocItems={tocItems} dropdownLinks={dropdownLinks} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={} > {children} diff --git a/src/layouts/md/Upgrade.tsx b/src/layouts/md/Upgrade.tsx index 40128e528f2..de874db2dc8 100644 --- a/src/layouts/md/Upgrade.tsx +++ b/src/layouts/md/Upgrade.tsx @@ -24,7 +24,11 @@ export const upgradeComponents = { type UpgradeLayoutProps = ChildOnlyProp & Pick< MdPageContent, - "slug" | "tocItems" | "lastEditLocaleTimestamp" | "contentNotTranslated" + | "slug" + | "tocItems" + | "lastEditLocaleTimestamp" + | "contentNotTranslated" + | "contributors" > & { frontmatter: UpgradeFrontmatter } @@ -35,6 +39,7 @@ export const UpgradeLayout = ({ tocItems, lastEditLocaleTimestamp, contentNotTranslated, + contributors, }: UpgradeLayoutProps) => { const { t } = useTranslation("page-upgrades") @@ -91,6 +96,8 @@ export const UpgradeLayout = ({ dir={contentNotTranslated ? "ltr" : "unset"} tocItems={tocItems} dropdownLinks={dropdownLinks} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={} > {children} diff --git a/src/layouts/md/UseCases.tsx b/src/layouts/md/UseCases.tsx index 79537a32130..3702fdf35ef 100644 --- a/src/layouts/md/UseCases.tsx +++ b/src/layouts/md/UseCases.tsx @@ -18,15 +18,30 @@ import { ContentLayout } from "../ContentLayout" import { useTranslation } from "@/hooks/useTranslation" +const CardGrid = (props: ChildOnlyProp) => ( +
+) + // UseCases layout components export const useCasesComponents = { + CardGrid, AiAgentProductLists, BuildYourOwnAIAgent, PredictionMarketLists, } type UseCasesLayoutProps = ChildOnlyProp & - Pick & { + Pick< + MdPageContent, + | "slug" + | "tocItems" + | "contentNotTranslated" + | "contributors" + | "lastEditLocaleTimestamp" + > & { frontmatter: UseCasesFrontmatter } export const UseCasesLayout = ({ @@ -35,6 +50,8 @@ export const UseCasesLayout = ({ slug, tocItems, contentNotTranslated, + contributors, + lastEditLocaleTimestamp, }: UseCasesLayoutProps) => { const { t } = useTranslation("template-usecase") @@ -138,6 +155,15 @@ export const UseCasesLayout = ({ eventName: "prediction-markets", }, }, + { + text: t("template-usecase:template-usecase-dropdown-rwa"), + href: "/real-world-assets/", + matomo: { + eventCategory: "use cases menu", + eventAction: "click", + eventName: "real-world-assets", + }, + }, ], } @@ -172,6 +198,8 @@ export const UseCasesLayout = ({ tocItems={tocItems} dropdownLinks={dropdownLinks} maxDepth={frontmatter.sidebarDepth} + contributors={contributors} + lastEditLocaleTimestamp={lastEditLocaleTimestamp} heroSection={} > {children} diff --git a/src/layouts/stories/ContentLayout.stories.tsx b/src/layouts/stories/ContentLayout.stories.tsx index 28f3911db7a..0fdd70ad47a 100644 --- a/src/layouts/stories/ContentLayout.stories.tsx +++ b/src/layouts/stories/ContentLayout.stories.tsx @@ -62,6 +62,21 @@ export const ContentLayout: StoryObj = { ], }, maxDepth: 2, + contributors: [ + { + login: "github", + avatar_url: "/", + html_url: "https://github.com", + date: "2025-04-20T12:00:00.000Z", + }, + { + login: "crowdin", + avatar_url: "/", + html_url: "https://crowdin.com", + date: "2025-04-20T12:00:00.000Z", + }, + ], + lastEditLocaleTimestamp: "MM DD, YY", heroSection: (
Hero section diff --git a/src/lib/api/fetchGitHistory.ts b/src/lib/api/fetchGitHistory.ts index 2f54f15c106..edf299252d5 100644 --- a/src/lib/api/fetchGitHistory.ts +++ b/src/lib/api/fetchGitHistory.ts @@ -46,7 +46,7 @@ async function fetchWithRateLimit(filepath: string): Promise { } // Fetch commit history and save it to a JSON file -export const fetchAndCacheGitContributors = async ( +export const fetchAndCacheGitHubContributors = async ( filepath: string, cache: CommitHistory ) => { diff --git a/src/lib/api/fetchRSS.ts b/src/lib/api/fetchRSS.ts index 09d6ee43d88..7525ff34340 100644 --- a/src/lib/api/fetchRSS.ts +++ b/src/lib/api/fetchRSS.ts @@ -130,7 +130,10 @@ export const fetchRSS = async (xmlUrl: string | string[]) => { */ export const fetchXml = async (url: string) => { try { - const response = await fetch(url) + const response = await fetch(url, { + headers: { Cookie: "", DNT: "1" }, // Empty cookie header and do-not-track + credentials: "omit", // Don't send or receive cookies + }) const xml = await response.text() let returnObject: Record = {} parseString(xml, (err, result) => { diff --git a/src/lib/md/compile.ts b/src/lib/md/compile.ts index 6aa64f5ea74..bdfda42dbc4 100644 --- a/src/lib/md/compile.ts +++ b/src/lib/md/compile.ts @@ -9,7 +9,7 @@ import remarkGfm from "remark-gfm" import remarkHeadingId from "remark-heading-id" import { CONTENT_DIR, CONTENT_PATH } from "../constants" -import { Frontmatter, TocNodeType } from "../types" +import { Frontmatter, Layout, TocNodeType } from "../types" import rehypeImg from "@/lib/md/rehypeImg" import remarkInferToc from "@/lib/md/remarkInferToc" @@ -80,3 +80,15 @@ export const compile = async ({ tocNodeItems, } } + +export const extractLayoutFromMarkdown = async ( + markdown: string +): Promise => { + const source = preprocessMarkdown(markdown) + + const { frontmatter } = await compileMDX({ + source, + options: { parseFrontmatter: true }, + }) + return frontmatter.template +} diff --git a/src/lib/md/data.ts b/src/lib/md/data.ts index 99690ac7d67..74e4093ca58 100644 --- a/src/lib/md/data.ts +++ b/src/lib/md/data.ts @@ -1,19 +1,21 @@ import { MDXRemoteProps } from "next-mdx-remote" import readingTime, { ReadTimeResults } from "reading-time" +import type { Layout } from "@/lib/types" import { CommitHistory, FileContributor, Frontmatter, Lang, - Layout, ToCItem, } from "@/lib/types" -import { getFileContributorInfo } from "@/lib/utils/contributors" +import { getMarkdownFileContributorInfo } from "@/lib/utils/contributors" import { getLocaleTimestamp } from "@/lib/utils/time" -import { compile } from "./compile" +import { getLayoutFromSlug } from "../utils/layout" + +import { compile, extractLayoutFromMarkdown } from "./compile" import { importMd } from "./import" const commitHistoryCache: CommitHistory = {} @@ -21,7 +23,8 @@ const commitHistoryCache: CommitHistory = {} interface GetPageDataParams { locale: string slug: string - components: MDXRemoteProps["components"] + baseComponents: MDXRemoteProps["components"] + componentsMapping: Record layout?: Layout scope?: Record } @@ -39,7 +42,8 @@ interface PageData { export async function getPageData({ locale, slug, - components, + baseComponents, + componentsMapping, layout: layoutFromProps, scope, }: GetPageDataParams): Promise { @@ -47,6 +51,17 @@ export async function getPageData({ // Import and compile markdown const { markdown, isTranslated } = await importMd(locale, slug) + // Determine layout first to finalize list of components + const layout = + layoutFromProps || + (await extractLayoutFromMarkdown(markdown)) || + getLayoutFromSlug(slug) + + const components: MDXRemoteProps["components"] = { + ...baseComponents, + ...(layout ? componentsMapping[layout] : {}), + } + const { content, frontmatter, tocNodeItems } = await compile({ markdown, slugArray, @@ -55,8 +70,6 @@ export async function getPageData({ scope, }) - const layout = layoutFromProps || frontmatter.template || "static" - // Process TOC items const tocItems = tocNodeItems.length === 1 && "items" in tocNodeItems[0] @@ -64,13 +77,13 @@ export async function getPageData({ : tocNodeItems // Get contributor information - const { contributors, lastUpdatedDate } = await getFileContributorInfo( - slug, - locale, - frontmatter.lang as string, - layout, - commitHistoryCache - ) + const { contributors, lastUpdatedDate } = + await getMarkdownFileContributorInfo( + slug, + locale, + frontmatter.lang as string, + commitHistoryCache + ) // Format timestamp const lastEditLocaleTimestamp = getLocaleTimestamp( diff --git a/src/lib/types.ts b/src/lib/types.ts index fa42dc75383..7f95bd9a8e8 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -395,7 +395,7 @@ export type FileContributor = { login: string avatar_url: string html_url: string - date?: string + date: string } type FilePath = string @@ -982,6 +982,11 @@ export type EventCardProps = { imageUrl?: string } +export type PageWithContributorsProps = { + contributors: FileContributor[] + lastEditLocaleTimestamp: string +} + export type BreakpointKey = keyof typeof screens export type MaturityLevel = diff --git a/src/lib/utils/contributors.ts b/src/lib/utils/contributors.ts index bb1ad128d6c..6222e0d887a 100644 --- a/src/lib/utils/contributors.ts +++ b/src/lib/utils/contributors.ts @@ -1,6 +1,6 @@ import { join } from "path" -import type { CommitHistory, FileContributor, Lang, Layout } from "@/lib/types" +import type { CommitHistory, FileContributor, Lang } from "@/lib/types" import { CONTENT_DIR, CONTENT_PATH, DEFAULT_LOCALE } from "@/lib/constants" @@ -8,41 +8,95 @@ import { convertToFileContributorFromCrowdin, getCrowdinContributors, } from "./crowdin" -import { getLastModifiedDate } from "./gh" +import { getAppPageLastCommitDate, getMarkdownLastCommitDate } from "./gh" +import { getLocaleTimestamp } from "./time" -import { fetchAndCacheGitContributors } from "@/lib/api/fetchGitHistory" +import { fetchAndCacheGitHubContributors } from "@/lib/api/fetchGitHistory" -export const getFileContributorInfo = async ( +export const getMarkdownFileContributorInfo = async ( slug: string, locale: string, fileLang: string, - layout: Layout, cache: CommitHistory ) => { const mdPath = join(CONTENT_PATH, slug) const mdDir = join(CONTENT_DIR, slug) - const gitContributors = await fetchAndCacheGitContributors( + const gitHubContributors = await fetchAndCacheGitHubContributors( join("/", mdDir, "index.md"), cache ) - const latestCommitDate = getLastModifiedDate(slug, locale!) - const gitHubLastEdit = gitContributors[0]?.date + const latestCommitDate = getMarkdownLastCommitDate(slug, locale!) + const gitHubLastEdit = gitHubContributors[0]?.date const lastUpdatedDate = gitHubLastEdit || latestCommitDate - const crowdinContributors = ["docs", "tutorial"].includes(layout) - ? convertToFileContributorFromCrowdin( - getCrowdinContributors(mdPath, locale as Lang) - ) - : [] + const crowdinContributors = convertToFileContributorFromCrowdin( + getCrowdinContributors(mdPath, locale as Lang) + ) - const useGitHubContributors: boolean = + const englishOnly: boolean = fileLang === DEFAULT_LOCALE || crowdinContributors.length === 0 - const contributors: FileContributor[] = useGitHubContributors - ? gitContributors - : crowdinContributors + const contributors: FileContributor[] = englishOnly + ? gitHubContributors + : [...crowdinContributors, ...gitHubContributors] return { contributors, lastUpdatedDate } } + +/** + * Returns an array of possible historical file paths for a given page, + * accounting for different directory structures and migrations over time. + * + * @param pagePath - The relative path of the page (without extension). + * @returns An array of strings representing all historical file paths for the page. + * + * @remarks + * This function is used to track all possible locations a page may have existed in the repository, + * which is useful for aggregating git history and contributor information. + * + * @note + * If a page is migrated or its location changes, ensure the new path is added to this list. + * This maintains a complete historical record for accurate git history tracking. + */ +const getAllHistoricalPaths = (pagePath: string): string[] => [ + join("src/pages", `${pagePath}.tsx`), + join("src/pages", pagePath, "index.tsx"), + join("src/pages/[locale]", `${pagePath}.tsx`), + join("src/pages/[locale]", pagePath, "index.tsx"), + join("app/[locale]", pagePath, "page.tsx"), + join("app/[locale]", pagePath, "_components", `${pagePath}.tsx`), +] + +export const getAppPageContributorInfo = async ( + pagePath: string, + locale: Lang, + cache: CommitHistory +) => { + // TODO: Incorporate Crowdin contributor information + + const gitHubContributors = await getAllHistoricalPaths(pagePath).reduce( + async (acc, path) => { + const contributors = await fetchAndCacheGitHubContributors(path, cache) + return [...(await acc), ...contributors] + }, + Promise.resolve([] as FileContributor[]) + ) + + const uniqueGitHubContributors = gitHubContributors.filter( + (contributor, index, self) => + index === self.findIndex((t) => t.login === contributor.login) + ) + + const latestCommitDate = getAppPageLastCommitDate(gitHubContributors) + const lastEditLocaleTimestamp = getLocaleTimestamp(locale, latestCommitDate) + + if (!uniqueGitHubContributors.length || !lastEditLocaleTimestamp) { + throw new Error( + `No contributors found, path: ${pagePath}, locale: ${locale}` + ) + } + + return { contributors: uniqueGitHubContributors, lastEditLocaleTimestamp } +} diff --git a/src/lib/utils/crowdin.ts b/src/lib/utils/crowdin.ts index 9da6717b48b..cdffedcfcd0 100644 --- a/src/lib/utils/crowdin.ts +++ b/src/lib/utils/crowdin.ts @@ -30,4 +30,5 @@ export const convertToFileContributorFromCrowdin = ( login: username, avatar_url: avatarUrl, html_url: `https://crowdin.com/profile/${username}`, + date: new Date(0).toString(), })) diff --git a/src/lib/utils/gh.ts b/src/lib/utils/gh.ts index c54c84bcd6b..d2b4dfd1fd1 100644 --- a/src/lib/utils/gh.ts +++ b/src/lib/utils/gh.ts @@ -4,6 +4,8 @@ import { join } from "path" import { CONTENT_DIR, DEFAULT_LOCALE, TRANSLATIONS_DIR } from "@/lib/constants" +import { FileContributor } from "../types" + const getGitLogFromPath = (path: string): string => { // git command to show file last commit info const gitCommand = `git log -1 -- ${path}` @@ -25,8 +27,27 @@ const extractDateFromGitLogInfo = (logInfo: string): string => { } } +export const getAppPageLastCommitDate = ( + gitHubContributors: FileContributor[] +) => + gitHubContributors + .reduce((latest, contributor) => { + const commitDate = new Date(contributor.date) + return commitDate > latest ? commitDate : latest + }, new Date(0)) + .toString() + +export const getLastGitCommitDateByPath = (path: string): string => { + if (!fs.existsSync(path)) throw new Error(`File not found: ${path}`) + const logInfo = getGitLogFromPath(path) + return extractDateFromGitLogInfo(logInfo) +} + // This util filters the git log to get the file last commit info, and then the commit date (last update) -export const getLastModifiedDate = (slug: string, locale: string): string => { +export const getMarkdownLastCommitDate = ( + slug: string, + locale: string +): string => { const translatedContentPath = join(TRANSLATIONS_DIR, locale, slug, "index.md") const contentIsNotTranslated = !fs.existsSync(translatedContentPath) let filePath = "" @@ -39,14 +60,7 @@ export const getLastModifiedDate = (slug: string, locale: string): string => { filePath = join(TRANSLATIONS_DIR, locale, slug, "index.md") } - const logInfo = getGitLogFromPath(filePath) - return extractDateFromGitLogInfo(logInfo) -} - -export const getLastModifiedDateByPath = (path: string): string => { - if (!fs.existsSync(path)) throw new Error(`File not found: ${path}`) - const logInfo = getGitLogFromPath(path) - return extractDateFromGitLogInfo(logInfo) + return getLastGitCommitDateByPath(filePath) } const LABELS_TO_SEARCH = [ diff --git a/src/lib/utils/layout.ts b/src/lib/utils/layout.ts new file mode 100644 index 00000000000..718a2ae1e65 --- /dev/null +++ b/src/lib/utils/layout.ts @@ -0,0 +1,5 @@ +export const getLayoutFromSlug = (slug: string) => { + if (slug.includes("developers/docs")) return "docs" + if (slug.includes("developers/tutorials")) return "tutorial" + return "static" +} diff --git a/src/lib/utils/metadata.ts b/src/lib/utils/metadata.ts index 4cc861f11ff..fa4ddb627f4 100644 --- a/src/lib/utils/metadata.ts +++ b/src/lib/utils/metadata.ts @@ -3,6 +3,7 @@ import { getTranslations } from "next-intl/server" import { DEFAULT_OG_IMAGE, SITE_URL } from "@/lib/constants" +import { isLocaleValidISO639_1 } from "./translations" import { getFullUrl } from "./url" import { routing } from "@/i18n/routing" @@ -71,10 +72,9 @@ export const getMetadata = async ({ languages: { "x-default": xDefault, ...Object.fromEntries( - routing.locales.map((locale) => [ - locale, - getFullUrl(locale, slugString), - ]) + routing.locales + .filter(isLocaleValidISO639_1) + .map((locale) => [locale, getFullUrl(locale, slugString)]) ), }, }, diff --git a/src/lib/utils/scrollIntoView.ts b/src/lib/utils/scrollIntoView.ts index 879e3252f04..b7e448c3fb3 100644 --- a/src/lib/utils/scrollIntoView.ts +++ b/src/lib/utils/scrollIntoView.ts @@ -1,8 +1,8 @@ export const scrollIntoView = ( - toId: string, + selector: string, options: ScrollIntoViewOptions = { behavior: "smooth", block: "start" } ): void => { - const element = document.getElementById(toId) + const element = document.querySelector(selector) if (!element) return diff --git a/src/lib/utils/translations.ts b/src/lib/utils/translations.ts index b4d0da6563d..822b81defcd 100644 --- a/src/lib/utils/translations.ts +++ b/src/lib/utils/translations.ts @@ -25,6 +25,10 @@ export const filterRealLocales = (locales: string[] | undefined) => { return locales?.filter((locale) => locale !== FAKE_LOCALE) || [] } +export const isLocaleValidISO639_1 = (locale: string) => { + return i18nConfig.find((language) => language.code === locale)?.validISO639_1 +} + // Overwrites the default Persian numbering of the Farsi language to use Hindu-Arabic numerals (0-9) // Context: https://github.com/ethereum/ethereum-org-website/pull/5490#pullrequestreview-892596553 export const getLocaleForNumberFormat = (locale: Lang): Lang =>