Compare commits

...

3 Commits

7 changed files with 926 additions and 509 deletions

View File

@ -20,9 +20,9 @@
"@ai-sdk/openai": "^1.1.13",
"@aws-sdk/client-s3": "^3.758.0",
"@aws-sdk/s3-request-presigner": "^3.758.0",
"@content-collections/core": "^0.8.0",
"@content-collections/mdx": "^0.2.0",
"@content-collections/next": "^0.2.4",
"@content-collections/core": "^0.8.2",
"@content-collections/mdx": "^0.2.2",
"@content-collections/next": "^0.2.6",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/sortable": "^10.0.0",
@ -83,8 +83,8 @@
"drizzle-orm": "^0.39.3",
"embla-carousel-react": "^8.5.2",
"framer-motion": "^12.4.7",
"fumadocs-core": "^15.1.2",
"fumadocs-ui": "^15.1.2",
"fumadocs-core": "^15.2.8",
"fumadocs-ui": "^15.2.8",
"geist": "^1.3.1",
"input-otp": "^1.4.2",
"lucide-react": "^0.483.0",

1277
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -5,25 +5,15 @@ import { websiteConfig } from '@/config/website';
import { docsI18nConfig } from '@/lib/docs/i18n';
import { source } from '@/lib/docs/source';
import { getUrlWithLocale } from '@/lib/urls/urls';
import { I18nProvider, type Translations } from 'fumadocs-ui/i18n';
import { DocsLayout } from 'fumadocs-ui/layouts/docs';
import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared';
import { BookIcon, HomeIcon } from 'lucide-react';
import { HomeIcon } from 'lucide-react';
import type { Locale } from 'next-intl';
import { getTranslations } from 'next-intl/server';
import type { ReactNode } from 'react';
import '@/styles/mdx.css';
// available languages that will be displayed on UI
// make sure `locale` is consistent with your i18n config
const locales = Object.entries(websiteConfig.i18n.locales).map(
([locale, data]) => ({
name: data.name,
locale,
})
);
interface DocsLayoutProps {
children: ReactNode;
params: Promise<{ locale: Locale }>;
@ -50,17 +40,6 @@ export default async function DocsRootLayout({
const { locale } = await params;
const t = await getTranslations({ locale, namespace: 'DocsPage' });
// Create translations object for fumadocs-ui from our message files
const translations: Partial<Translations> = {
toc: t('toc'),
search: t('search'),
lastUpdate: t('lastUpdate'),
searchNoResult: t('searchNoResult'),
previousPage: t('previousPage'),
nextPage: t('nextPage'),
chooseLanguage: t('chooseLanguage'),
};
// Docs layout configurations
const showLocaleSwitch = Object.keys(websiteConfig.i18n.locales).length > 1;
const docsOptions: BaseLayoutProps = {
@ -85,14 +64,14 @@ export default async function DocsRootLayout({
},
...(websiteConfig.metadata.social?.twitter
? [
{
type: 'icon' as const,
icon: <XTwitterIcon />,
text: 'X',
url: websiteConfig.metadata.social.twitter,
secondary: true,
},
]
{
type: 'icon' as const,
icon: <XTwitterIcon />,
text: 'X',
url: websiteConfig.metadata.social.twitter,
secondary: true,
},
]
: []),
],
themeSwitch: {
@ -103,10 +82,8 @@ export default async function DocsRootLayout({
};
return (
<I18nProvider locales={locales} locale={locale} translations={translations}>
<DocsLayout tree={source.pageTree[locale]} {...docsOptions}>
{children}
</DocsLayout>
</I18nProvider>
<DocsLayout tree={source.pageTree[locale]} {...docsOptions}>
{children}
</DocsLayout>
);
}

View File

@ -52,7 +52,7 @@ export default async function LocaleLayout({
)}
>
<NextIntlClientProvider>
<Providers>
<Providers locale={locale}>
{children}
<Toaster richColors position="top-right" offset={64} />

View File

@ -4,9 +4,16 @@ import { ActiveThemeProvider } from '@/components/layout/active-theme-provider';
import { PaymentProvider } from '@/components/layout/payment-provider';
import { TooltipProvider } from '@/components/ui/tooltip';
import { websiteConfig } from '@/config/website';
import { Translations } from 'fumadocs-ui/i18n';
import { RootProvider } from 'fumadocs-ui/provider';
import { useTranslations } from 'next-intl';
import { ThemeProvider, useTheme } from 'next-themes';
import type { PropsWithChildren } from 'react';
import type { ReactNode } from 'react';
interface ProvidersProps {
children: ReactNode;
locale: string;
}
/**
* Providers
@ -19,10 +26,31 @@ import type { PropsWithChildren } from 'react';
* - TooltipProvider: Provides the tooltip to the app.
* - PaymentProvider: Provides the payment state to the app.
*/
export function Providers({ children }: PropsWithChildren) {
export function Providers({ children, locale }: ProvidersProps) {
const theme = useTheme();
const defaultMode = websiteConfig.metadata.mode?.defaultMode ?? 'system';
// available languages that will be displayed in the docs UI
// make sure `locale` is consistent with your i18n config
const locales = Object.entries(websiteConfig.i18n.locales).map(
([locale, data]) => ({
name: data.name,
locale,
})
);
// translations object for fumadocs-ui from our message files
const t = useTranslations('DocsPage');
const translations: Partial<Translations> = {
toc: t('toc'),
search: t('search'),
lastUpdate: t('lastUpdate'),
searchNoResult: t('searchNoResult'),
previousPage: t('previousPage'),
nextPage: t('nextPage'),
chooseLanguage: t('chooseLanguage'),
};
return (
<ThemeProvider
attribute="class"
@ -31,7 +59,7 @@ export function Providers({ children }: PropsWithChildren) {
disableTransitionOnChange
>
<ActiveThemeProvider>
<RootProvider theme={theme}>
<RootProvider theme={theme} i18n={{ locale, locales, translations }}>
<TooltipProvider>
<PaymentProvider>{children}</PaymentProvider>
</TooltipProvider>

View File

@ -55,57 +55,19 @@ const searchAPI = createI18nSearchAPI('advanced', {
});
/**
* Fumadocs 15.2.8 fixed the bug that the `locale` is not passed to the search API
*
* ref:
* https://x.com/indie_maker_fox/status/1913457083997192589
*
* NOTICE:
* Fumadocs 15.1.2 has a bug that the `locale` is not passed to the search API
* 1. Wrap the GET handler for debugging docs search
* 2. Detect locale from referer header, and add the locale parameter to the search API
* 3. Fumadocs core searchAPI get `locale` from searchParams, and pass it to the search API
* https://github.com/fuma-nama/fumadocs/blob/dev/packages/core/src/search/orama/create-endpoint.ts#L19
*/
export const GET = async (request: Request) => {
const url = new URL(request.url);
const query = url.searchParams.get('query') || '';
let locale = url.searchParams.get('locale') || docsI18nConfig.defaultLanguage;
// detect locale from referer header
const referer = request.headers.get('referer');
if (referer) {
try {
const refererUrl = new URL(referer);
console.log('search, referer pathname:', refererUrl.pathname);
const refererPathParts = refererUrl.pathname.split('/').filter(Boolean);
console.log('search, referer path parts:', refererPathParts);
if (
refererPathParts.length > 0 &&
docsI18nConfig.languages.includes(refererPathParts[0])
) {
locale = refererPathParts[0];
console.log(`search, detected locale from referer: ${locale}`);
}
} catch (e) {
console.error('search, error parsing referer:', e);
}
}
console.log(`search, request: query="${query}", detected locale="${locale}"`);
// ensure locale parameter is passed to search API
const searchUrl = new URL(url);
searchUrl.searchParams.set('locale', locale);
const modifiedRequest = new Request(searchUrl, {
headers: request.headers,
method: request.method,
body: request.body,
cache: request.cache,
credentials: request.credentials,
integrity: request.integrity,
keepalive: request.keepalive,
mode: request.mode,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
signal: request.signal,
});
const response = await searchAPI.GET(modifiedRequest);
const response = await searchAPI.GET(request);
return response;
};

View File

@ -28,7 +28,16 @@ export default function Example() {
className="bg-transparent px-4 py-2 text-sm focus-visible:outline-none"
/>
</div>
<DynamicCodeBlock lang={lang} code={code} options={{}} />
<DynamicCodeBlock
lang={lang}
code={code}
options={{
themes: {
light: 'github-light',
dark: 'github-dark'
}
}}
/>
</div>
);
}