Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
4 changes: 4 additions & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ const baseNextConfig = {
*/
serverSourceMaps: false,
},
i18n: {
locales: ["en-US", "zh-CN"],
defaultLocale: "en-US",
},
trailingSlash: true,
basePath: process.env.NEXT_PUBLIC_BASE_PATH,
// This is required to support PostHog trailing slash API requests
Expand Down
245 changes: 126 additions & 119 deletions public/sitemap-0.xml

Large diffs are not rendered by default.

38 changes: 25 additions & 13 deletions src/components/shared/components/Hero.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import clsx from "clsx";
import Image from "next/image";
import { useRouter } from "next/router";
import { useConfig } from "nextra-theme-docs";
import { useMemo } from "react";
import { useSelector } from "react-redux";
import tw, { styled } from "twin.macro";

import { useCurrentBreakpoint } from "~/hooks/useCurrentBreakpoint";
import { useIsHomePage, useNormalizedRoute } from "~/hooks/useIsHomePage";
import { basePath } from "~/lib/app.constants";
import { selectDirectoriesByRoute } from "~/lib/directories/directories.selectors";

Expand Down Expand Up @@ -38,25 +39,36 @@ export const StyledHero = styled.div`
* ---
*/
export const Hero: React.FC = () => {
const { route } = useRouter();
const { upLg } = useCurrentBreakpoint();
const normalizedRoute = useNormalizedRoute();

// Use Nextra's useConfig for locale-aware title and frontMatter
const config = useConfig();
const nextraTitle = config.title;
const nextraFrontMatter = config.frontMatter || {};

const directoriesByRoute = useSelector(selectDirectoriesByRoute);
const currentDirectory = useMemo(() => directoriesByRoute[route], [directoriesByRoute, route]);
const currentDirectory = useMemo(() => directoriesByRoute[normalizedRoute], [directoriesByRoute, normalizedRoute]);

const isMainPage = useMemo(() => mainNavRoutes.includes(route), [route]);
const isHomePage = useMemo(() => ["/"].includes(route), [route]);
const isMainPage = useMemo(() => mainNavRoutes.includes(normalizedRoute), [normalizedRoute]);
const isHomePage = useIsHomePage();
const isSubCategoryPage = useMemo(
() => currentDirectory?.frontMatter?.pageType === "sub-category",
[currentDirectory]
() => nextraFrontMatter?.pageType === "sub-category" || currentDirectory?.frontMatter?.pageType === "sub-category",
[nextraFrontMatter, currentDirectory]
);

const { title, description, readTime, readType, imgUrl, imgWidth } = useMemo(() => {
if (!currentDirectory) return {};

const { frontMatter, meta } = currentDirectory;

const title = frontMatter?.title ? String(frontMatter.title) : meta?.title ? String(meta.title) : undefined;
// Prefer Nextra's locale-aware frontMatter and title
const frontMatter = nextraFrontMatter;
const meta = currentDirectory?.meta;

const title = frontMatter?.title
? String(frontMatter.title)
: nextraTitle
? String(nextraTitle)
: meta?.title
? String(meta.title)
: undefined;

const description = frontMatter?.description
? String(frontMatter.description)
Expand Down Expand Up @@ -96,7 +108,7 @@ export const Hero: React.FC = () => {
imgUrl,
imgWidth,
};
}, [currentDirectory]);
}, [nextraFrontMatter, nextraTitle, currentDirectory]);

if (isHomePage || !title) return null;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useRouter } from "next/router";
import { PropsWithChildren, useEffect, useMemo, useState } from "react";
import tw, { styled } from "twin.macro";

import { useIsHomePage, useNormalizedRoute } from "~/hooks/useIsHomePage";
import { useScrollToPageTop } from "~/hooks/useScrollToPageTop";

import { mainNavRoutes } from "../Layout.constants";
Expand Down Expand Up @@ -195,9 +195,9 @@ type LayoutProps = {
};

export const Layout: React.FC<PropsWithChildren<LayoutProps>> = ({ className, children }) => {
const { route } = useRouter();
const isMainPage = useMemo(() => mainNavRoutes.includes(route), [route]);
const isHomePage = useMemo(() => route === "/", [route]);
const normalizedRoute = useNormalizedRoute();
const isMainPage = useMemo(() => mainNavRoutes.includes(normalizedRoute), [normalizedRoute]);
const isHomePage = useIsHomePage();

useScrollToPageTop();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import clsx from "clsx";
import { useRouter } from "next/router";
import { PropsWithChildren, useMemo } from "react";
import { PropsWithChildren } from "react";

import { useIsHomePage } from "~/hooks/useIsHomePage";
import { useSetDirectoriesState } from "~/hooks/useSetDirectoriesState";

import { getGitHubEditUrl } from "../../../../../lib/github-edit-url";
Expand All @@ -18,8 +19,7 @@ type MainContentWrapperProps = PropsWithChildren<{}>;
export const MainContentWrapper: React.FC<MainContentWrapperProps> = ({ children }) => {
useSetDirectoriesState();
const { route } = useRouter();

const isHomePage = useMemo(() => route === "/", [route]);
const isHomePage = useIsHomePage();

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import List from "@mui/material/List";
import clsx from "clsx";
import { motion } from "framer-motion";
import Link from "next/link";
import { useRouter } from "next/router";
import { Page } from "nextra";
import { getPagesUnderRoute } from "nextra/context";
import { PropsWithChildren, useCallback, useEffect, useMemo, useState } from "react";
import { useSelector } from "react-redux";

import { useCurrentBreakpoint } from "~/hooks/useCurrentBreakpoint";
import { selectPages } from "~/lib/directories/directories.selectors";
import { getRevealProps } from "~/lib/helpers/animations";

import { Footer } from "../../Footer";
Expand All @@ -24,13 +25,53 @@ type NavigationLayoutProps = PropsWithChildren<{

export const NavigationLayout: React.FC<NavigationLayoutProps> = ({ isMainPage, children }) => {
const { upSm } = useCurrentBreakpoint();
const { locale, defaultLocale } = useRouter();

const [isLeftDrawerOpen, setIsLeftDrawerOpen] = useState(true);

const closeMobileDrawer = useCallback(() => !upSm && setIsLeftDrawerOpen(false), [upSm, setIsLeftDrawerOpen]);

const pages = useSelector(selectPages);
const navPages = useMemo(() => pages.filter((page) => page.kind === "Folder"), [pages]);
// Use Nextra's getPagesUnderRoute for locale-aware page data
const getNavSectionPages = useCallback(
(route: string) => {
const pages = getPagesUnderRoute(route);

// Check if page name contains locale suffix (e.g., "zetachain.zh-CN")
const getPageLocale = (page: Page): string | undefined => {
if ("locale" in page && page.locale) return page.locale as string;
// Check name for locale suffix
const nameParts = page.name.split(".");
if (nameParts.length > 1) {
const suffix = nameParts[nameParts.length - 1];
if (suffix === "en-US" || suffix === "zh-CN") return suffix;
}
return undefined;
};

// Filter pages: only keep pages matching current locale, deduplicate by route
const routeToPage = new Map<string, Page>();

pages.forEach((page) => {
// Skip index pages
if (page.name === "index" || page.name.startsWith("index.")) return;

const pageLocale = getPageLocale(page);

// Only process pages that match current locale (or have no locale suffix for default)
const isCurrentLocale = pageLocale === locale;
const isDefaultForNoLocale = !pageLocale && locale === defaultLocale;
if (!isCurrentLocale && !isDefaultForNoLocale && pageLocale) return;

const existing = routeToPage.get(page.route);
if (!existing) {
routeToPage.set(page.route, page);
}
});

return Array.from(routeToPage.values());
},
[locale, defaultLocale]
);

// To prevent a flash of the drawer on first render given that useCurrentBreakpoint has an issue always returning false for the first render for upLg and others
useEffect(() => {
Expand Down Expand Up @@ -76,26 +117,24 @@ export const NavigationLayout: React.FC<NavigationLayoutProps> = ({ isMainPage,
<div key={items[0].url} className="flex flex-col">
<List className="w-full font-medium">
{items.map((item) => {
const navSection = navPages.find((page) => page.route === item.url);
const navSectionPages = item.url ? getNavSectionPages(item.url) : [];

return (
<div key={item.url}>
<NavigationItem item={item} isOpen={isLeftDrawerOpen} onClick={closeMobileDrawer} />

{!!navSection && "children" in navSection && (
{navSectionPages.length > 0 && (
<List className="w-full">
{navSection.children
.filter((page) => page.route !== item.url)
.map((page) => (
<div key={page.route} className="px-3 pl-12 sm:pr-6 pb-3 sm:pb-2">
<NavigationAccordionLink
key={page.route}
page={page}
onClick={closeMobileDrawer}
isTopLevelPage
/>
</div>
))}
{navSectionPages.map((page) => (
<div key={page.route} className="px-3 pl-12 sm:pr-6 pb-3 sm:pb-2">
<NavigationAccordionLink
key={page.route}
page={page}
onClick={closeMobileDrawer}
isTopLevelPage
/>
</div>
))}
</List>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import Image from "next/image";
import Link from "next/link";
import { useRouter } from "next/router";
import { PropsWithChildren, useMemo } from "react";
import { useSelector } from "react-redux";
import { usePrevious } from "react-use";

import { useNormalizedRoute } from "~/hooks/useIsHomePage";
import { basePath } from "~/lib/app.constants";
import { selectDirectoriesByRoute, selectFlatDirectories } from "~/lib/directories/directories.selectors";
import { countRouteSegments, getValidParentDirectory } from "~/lib/helpers/nextra";
Expand All @@ -21,22 +21,24 @@ type PrevNextNavigationWrapperProps = PropsWithChildren<{}>;
* @todo Add optional "tutorial" link in the bottom Continue Learning section
*/
export const PrevNextNavigationWrapper: React.FC<PrevNextNavigationWrapperProps> = ({ children }) => {
const { route } = useRouter();
const normalizedRoute = useNormalizedRoute();

const prevRoute = usePrevious(route);
const prevNormalizedRoute = usePrevious(normalizedRoute);

const flatDirectories = useSelector(selectFlatDirectories);
const directoriesByRoute = useSelector(selectDirectoriesByRoute);

const { prevPage, nextPage, relatedTutorial } = useMemo(() => {
const currentDirectory = directoriesByRoute[route];
const currentDirectory = directoriesByRoute[normalizedRoute];

if (!route || !currentDirectory) return { prevPage: null, nextPage: null };
if (!normalizedRoute || !currentDirectory) return { prevPage: null, nextPage: null };

const isPrevRouteCurrent = prevRoute === route;
const isPrevRouteChildren = prevRoute ? countRouteSegments(prevRoute) > countRouteSegments(route) : false;
const isPrevRouteValid = prevRoute && !isPrevRouteCurrent && !isPrevRouteChildren;
const prevDirectory = isPrevRouteValid ? directoriesByRoute[prevRoute] : null;
const isPrevRouteCurrent = prevNormalizedRoute === normalizedRoute;
const isPrevRouteChildren = prevNormalizedRoute
? countRouteSegments(prevNormalizedRoute) > countRouteSegments(normalizedRoute)
: false;
const isPrevRouteValid = prevNormalizedRoute && !isPrevRouteCurrent && !isPrevRouteChildren;
const prevDirectory = isPrevRouteValid ? directoriesByRoute[prevNormalizedRoute] : null;

const nextPage = flatDirectories[currentDirectory.index + 1] || null;

Expand Down Expand Up @@ -72,13 +74,13 @@ export const PrevNextNavigationWrapper: React.FC<PrevNextNavigationWrapperProps>
nextPage,
relatedTutorial,
};
}, [flatDirectories, directoriesByRoute, route, prevRoute]);
}, [flatDirectories, directoriesByRoute, normalizedRoute, prevNormalizedRoute]);

const isMainPage = useMemo(() => mainNavRoutes.includes(route), [route]);
const isMainPage = useMemo(() => mainNavRoutes.includes(normalizedRoute), [normalizedRoute]);

const isSubCategoryPage = useMemo(
() => directoriesByRoute[route]?.frontMatter?.pageType === "sub-category",
[directoriesByRoute, route]
() => directoriesByRoute[normalizedRoute]?.frontMatter?.pageType === "sub-category",
[directoriesByRoute, normalizedRoute]
);

const continueLearningNavItems = useMemo(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import clsx from "clsx";
import { useRouter } from "next/router";
import { useMemo } from "react";

import { useCurrentBreakpoint } from "~/hooks/useCurrentBreakpoint";
import { useNormalizedRoute } from "~/hooks/useIsHomePage";
import { NavigationSectionVariant } from "~/lib/helpers/nextra";

import { mainNavRoutes } from "../../Layout";
Expand All @@ -28,8 +28,8 @@ export const NavigationSection: React.FC<NavigationSectionProps> = ({
lastItemEmbellishment,
}) => {
const { upXl } = useCurrentBreakpoint();
const { route } = useRouter();
const isMainPage = useMemo(() => mainNavRoutes.includes(route), [route]);
const normalizedRoute = useNormalizedRoute();
const isMainPage = useMemo(() => mainNavRoutes.includes(normalizedRoute), [normalizedRoute]);

if (variant === "fancy" && upXl) {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useRouter } from "next/router";
import { PropsWithChildren, useEffect, useMemo, useState } from "react";
import { useSelector } from "react-redux";

import { useNormalizedRoute } from "~/hooks/useIsHomePage";
import { selectDirectoriesByRoute } from "~/lib/directories/directories.selectors";

import { mainNavRoutes } from "../../Layout";
Expand All @@ -12,11 +13,12 @@ type TableOfContentsWrapperProps = PropsWithChildren<{}>;

export const TableOfContentsWrapper: React.FC<TableOfContentsWrapperProps> = ({ children }) => {
const { route } = useRouter();
const normalizedRoute = useNormalizedRoute();
const directoriesByRoute = useSelector(selectDirectoriesByRoute);

const [headings, setHeadings] = useState<Heading[]>([]);

const isMainPage = useMemo(() => mainNavRoutes.includes(route), [route]);
const isMainPage = useMemo(() => mainNavRoutes.includes(normalizedRoute), [normalizedRoute]);
const isSubCategoryPage = useMemo(
() => directoriesByRoute[route]?.frontMatter?.pageType === "sub-category",
[directoriesByRoute, route]
Expand Down
40 changes: 40 additions & 0 deletions src/hooks/useIsHomePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useRouter } from "next/router";
import { useMemo } from "react";

const stripLocaleSuffix = (segment: string, locale?: string) => {
if (!locale) return segment;

const suffix = `.${locale}`;
return segment.endsWith(suffix) ? segment.slice(0, -suffix.length) : segment;
};

const buildNormalizedRoute = (route: string, locale?: string) => {
if (!route || route === "/") return "/";

const segments = route
.split("/")
.filter(Boolean)
.map((segment) => stripLocaleSuffix(segment, locale));

if (!segments.length) return "/";

if (segments[segments.length - 1] === "index") {
segments.pop();
}

if (!segments.length) return "/";

return `/${segments.join("/")}`;
};

export const useNormalizedRoute = () => {
const { route, locale } = useRouter();

return useMemo(() => buildNormalizedRoute(route, locale), [route, locale]);
};

export const useIsHomePage = () => {
const normalizedRoute = useNormalizedRoute();

return normalizedRoute === "/";
};
Loading
Loading