Merge pull request #15 from MkSaaSHQ/docs

feat: docs support theme and i18n
This commit is contained in:
javayhu 2025-03-29 12:33:30 +08:00 committed by GitHub
commit 02af1c7512
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 190 additions and 80 deletions

View File

@ -24,7 +24,8 @@ import {
/**
* Fumadocs documentation
* 1. https://fumadocs.com/docs/configuration
*
* https://fumadocs.vercel.app/docs/headless/content-collections
*/
const docs = defineCollection({
name: 'docs',

View File

@ -3,11 +3,15 @@ title: Hello World
description: Your first document
---
Welcome to the docs! You can start writing documents in `/content/docs`.
Hey there!
## What is Next?
## Heading
<Cards>
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.vercel.app" />
</Cards>
### Heading
#### Heading

View File

@ -0,0 +1,8 @@
---
title: 中文
description: 您的第一個文檔
---
## Hi 中文
Fumadocs 對 i18n 有良好的支持

View File

@ -1,17 +1,21 @@
---
title: Components
description: Components
title: Test Document
description: Your first document
---
## Code Block
Hey there!
```js
console.log('Hello World');
```
## Cards
## Heading
<Cards>
<Card title="Learn more about Next.js" href="https://nextjs.org/docs" />
<Card title="Learn more about Fumadocs" href="https://fumadocs.vercel.app" />
</Cards>
### Heading
```js
console.log('Hello World');
```
#### Heading

View File

@ -1,4 +1,4 @@
import { source } from '@/lib/source';
import { source } from '@/lib/docs/source';
import { MDXContent } from '@content-collections/mdx/react';
import defaultMdxComponents, { createRelativeLink } from 'fumadocs-ui/mdx';
import {
@ -9,12 +9,14 @@ import {
} from 'fumadocs-ui/page';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { Locale } from 'next-intl';
export default async function Page(props: {
params: Promise<{ slug?: string[] }>;
export default async function Page({
params,
}: {
params: { slug?: string[]; locale: Locale };
}) {
const params = await props.params;
const page = source.getPage(params.slug);
const page = source.getPage(params.slug, params.locale);
if (!page) notFound();
return (
@ -40,11 +42,12 @@ export function generateStaticParams() {
return source.generateParams();
}
export async function generateMetadata(props: {
params: Promise<{ slug?: string[] }>;
export async function generateMetadata({
params,
}: {
params: { slug?: string[]; locale: Locale };
}) {
const params = await props.params;
const page = source.getPage(params.slug);
const page = source.getPage(params.slug, params.locale);
if (!page) notFound();
return {

View File

@ -0,0 +1,36 @@
import { ThemeSwitcher } from '@/components/layout/theme-switcher';
import { Logo } from '@/components/logo';
import { websiteConfig } from '@/config';
import { defaultMessages } from '@/i18n/messages';
import { docsI18nConfig } from '@/lib/docs/i18n';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
/**
* Docs layout configurations
*
* https://fumadocs.vercel.app/docs/ui/layouts/docs
*/
export const baseOptions: BaseLayoutProps = {
i18n: docsI18nConfig,
githubUrl: websiteConfig.social.github ?? undefined,
nav: {
title: (
<>
<Logo className="size-6" />
{defaultMessages.Metadata.name}
</>
),
},
links: [
{
text: 'Homepage',
url: '/',
active: 'nested-url',
}
],
themeSwitch: {
enabled: true,
mode: 'light-dark-system',
component: <ThemeSwitcher />
},
};

View File

@ -0,0 +1,72 @@
import { baseOptions } from '@/app/[locale]/docs/layout.config';
import { source } from '@/lib/docs/source';
import { Translations } from 'fumadocs-ui/i18n';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { Locale } from 'next-intl';
import type { ReactNode } from 'react';
import { DocsProviders } from './providers-docs';
import { I18nProvider } from 'fumadocs-ui/i18n';
import '@/styles/docs.css';
const zhTranslations: Partial<Translations> = {
toc: '目录',
search: '搜索文档',
lastUpdate: '最后更新于',
searchNoResult: '没有结果',
previousPage: '上一页',
nextPage: '下一页',
chooseLanguage: '选择语言',
};
const enTranslations: Partial<Translations> = {
toc: 'Table of Contents',
search: 'Search docs',
lastUpdate: 'Last updated on',
searchNoResult: 'No results',
previousPage: 'Previous',
nextPage: 'Next',
chooseLanguage: 'Select language',
};
// Map of locale to translations
const translations: Record<string, Partial<Translations>> = {
zh: zhTranslations,
en: enTranslations,
};
// available languages that will be displayed on UI
// make sure `locale` is consistent with your i18n config
const locales = [
{
name: 'English',
locale: 'en',
},
{
name: 'Chinese',
locale: 'zh',
},
];
interface DocsLayoutProps {
children: ReactNode;
params: Promise<{ locale: Locale }>;
}
export default async function DocsRootLayout({ children, params }: DocsLayoutProps) {
const { locale } = await params;
return (
<DocsProviders>
<I18nProvider
locales={locales}
locale={locale}
translations={translations[locale] || enTranslations}
>
<DocsLayout tree={source.pageTree[locale]} {...baseOptions}>
{children}
</DocsLayout>
</I18nProvider>
</DocsProviders>
);
}

View File

@ -0,0 +1,24 @@
'use client';
import { RootProvider } from 'fumadocs-ui/provider';
import { useTheme } from 'next-themes';
import { ReactNode } from 'react';
interface DocsProvidersProps {
children: ReactNode;
}
/***
* Docs Configuration
*
* https://fumadocs.vercel.app/docs/ui/theme#lightdark-modes
*/
export function DocsProviders({ children }: DocsProvidersProps) {
const theme = useTheme();
return (
<RootProvider theme={theme}>
{children}
</RootProvider>
);
}

View File

@ -34,7 +34,7 @@ export default async function LocaleLayout({
}
return (
<html lang={locale} suppressHydrationWarning>
<html suppressHydrationWarning lang={locale}>
<body
suppressHydrationWarning
className={cn(

View File

@ -1,4 +1,4 @@
import { source } from '@/lib/source';
import { source } from '@/lib/docs/source';
import { createFromSource } from 'fumadocs-core/search/server';
export const { GET } = createFromSource(source);

View File

@ -1,34 +0,0 @@
import { defaultMessages } from '@/i18n/messages';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
/**
* Shared layout configurations
*
* you can customise layouts individually from:
* Home Layout: app/(home)/layout.tsx
* Docs Layout: app/docs/layout.tsx
*/
export const baseOptions: BaseLayoutProps = {
nav: {
title: (
<>
<svg
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
aria-label="Logo"
>
<circle cx={12} cy={12} r={12} fill="currentColor" />
</svg>
{defaultMessages.Metadata.name}
</>
),
},
links: [
{
text: 'Documentation',
url: '/docs',
active: 'nested-url',
},
],
};

View File

@ -1,22 +0,0 @@
import { baseOptions } from '@/app/docs/layout.config';
import { source } from '@/lib/source';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import { RootProvider } from 'fumadocs-ui/provider';
import type { ReactNode } from 'react';
import { fontDMSans } from '@/assets/fonts';
import '@/styles/docs.css';
export default function Layout({ children }: { children: ReactNode }) {
return (
<html lang="en" className={fontDMSans.className} suppressHydrationWarning>
<body className="flex flex-col min-h-screen">
<RootProvider>
<DocsLayout tree={source.pageTree} {...baseOptions}>
{children}
</DocsLayout>
</RootProvider>
</body>
</html>
);
}

12
src/lib/docs/i18n.ts Normal file
View File

@ -0,0 +1,12 @@
import { DEFAULT_LOCALE, LOCALES } from '@/i18n/routing';
import type { I18nConfig } from 'fumadocs-core/i18n';
/**
* Internationalization configuration for FumaDocs
*
* https://fumadocs.vercel.app/docs/ui/internationalization
*/
export const docsI18nConfig: I18nConfig = {
defaultLanguage: DEFAULT_LOCALE,
languages: LOCALES,
};

View File

@ -1,8 +1,10 @@
import { createMDXSource } from '@fumadocs/content-collections';
import { allDocs, allMetas } from 'content-collections';
import { loader } from 'fumadocs-core/source';
import { createMDXSource } from '@fumadocs/content-collections';
import { docsI18nConfig } from './i18n';
export const source = loader({
baseUrl: '/docs',
i18n: docsI18nConfig,
source: createMDXSource(allDocs, allMetas),
});

View File

@ -23,6 +23,6 @@ export const config = {
// (e.g. `/pathnames` -> `/zh/pathnames`)
// Exclude API routes and other Next.js internal routes
// if not exclude api routes, auth routes will not work
'/((?!api|_next|_vercel|docs|.*\\..*).*)',
'/((?!api|_next|_vercel|.*\\..*).*)',
],
};