diff options
Diffstat (limited to 'packages/docs/app')
| -rw-r--r-- | packages/docs/app/docs/page.tsx | 51 | ||||
| -rw-r--r-- | packages/docs/app/docs/search.ts | 7 | ||||
| -rw-r--r-- | packages/docs/app/lib/i18n.ts | 8 | ||||
| -rw-r--r-- | packages/docs/app/lib/layout.shared.tsx | 19 | ||||
| -rw-r--r-- | packages/docs/app/lib/source.ts | 5 | ||||
| -rw-r--r-- | packages/docs/app/root.tsx | 47 | ||||
| -rw-r--r-- | packages/docs/app/routes.ts | 17 | ||||
| -rw-r--r-- | packages/docs/app/routes/docs.tsx | 16 | ||||
| -rw-r--r-- | packages/docs/app/routes/home.tsx | 177 | ||||
| -rw-r--r-- | packages/docs/app/routes/not-found.tsx | 7 |
10 files changed, 299 insertions, 55 deletions
diff --git a/packages/docs/app/docs/page.tsx b/packages/docs/app/docs/page.tsx index 2618989..a9e3433 100644 --- a/packages/docs/app/docs/page.tsx +++ b/packages/docs/app/docs/page.tsx @@ -1,51 +1,58 @@ import type { Route } from './+types/page'; +import defaultMdxComponents from 'fumadocs-ui/mdx'; import { DocsLayout } from 'fumadocs-ui/layouts/docs'; -import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/layouts/docs/page'; +import { DocsPage, DocsBody, DocsDescription, DocsTitle } from 'fumadocs-ui/page'; +import { Card, Cards } from 'fumadocs-ui/components/card'; import { source } from '@/lib/source'; -import defaultMdxComponents from 'fumadocs-ui/mdx'; -import browserCollections from 'fumadocs-mdx:collections/browser'; +import { i18n } from '@/lib/i18n'; import { baseOptions } from '@/lib/layout.shared'; import { useFumadocsLoader } from 'fumadocs-core/source/client'; +import browserCollections from 'fumadocs-mdx:collections/browser'; export async function loader({ params }: Route.LoaderArgs) { - const slugs = params['*'].split('/').filter((v) => v.length > 0); - const page = source.getPage(slugs); - if (!page) throw new Response('Not found', { status: 404 }); + // 从路由参数获取语言,如果没有则使用默认语言 + // URL 格式: /docs/getting-started (默认语言 zh) + // URL 格式: /en/docs/getting-started (英语) + const lang = (params.lang && i18n.languages.includes(params.lang as any)) + ? (params.lang as 'zh' | 'en') + : (i18n.defaultLanguage as 'zh' | 'en'); + + // 获取文档路径 slugs + const slugs = params['*']?.split('/').filter((v) => v.length > 0) || []; + + const page = source.getPage(slugs, lang); + + if (!page) { + throw new Response('Not found', { status: 404 }); + } return { path: page.path, - pageTree: await source.serializePageTree(source.getPageTree()), + pageTree: await source.serializePageTree(source.getPageTree(lang)), + lang, }; } const clientLoader = browserCollections.docs.createClientLoader({ - component( - { toc, frontmatter, default: Mdx }, - // you can define props for the `<Content />` component - props?: { - className?: string; - }, - ) { + component({ toc, frontmatter, default: Mdx }) { return ( - <DocsPage toc={toc} {...props}> - <title>{frontmatter.title}</title> - <meta name="description" content={frontmatter.description} /> + <DocsPage toc={toc}> <DocsTitle>{frontmatter.title}</DocsTitle> <DocsDescription>{frontmatter.description}</DocsDescription> <DocsBody> - <Mdx components={{ ...defaultMdxComponents }} /> + <Mdx components={{ ...defaultMdxComponents, Card, Cards }} /> </DocsBody> </DocsPage> ); }, }); -export default function Page({ loaderData }: Route.ComponentProps) { - const { path, pageTree } = useFumadocsLoader(loaderData); +export default function Page({ loaderData, params }: Route.ComponentProps) { + const { pageTree, lang } = useFumadocsLoader(loaderData); return ( - <DocsLayout {...baseOptions()} tree={pageTree}> - {clientLoader.useContent(path)} + <DocsLayout {...baseOptions(lang)} tree={pageTree}> + {clientLoader.useContent(loaderData.path)} </DocsLayout> ); } diff --git a/packages/docs/app/docs/search.ts b/packages/docs/app/docs/search.ts index 9603c72..a98edd5 100644 --- a/packages/docs/app/docs/search.ts +++ b/packages/docs/app/docs/search.ts @@ -3,8 +3,11 @@ import { createFromSource } from 'fumadocs-core/search/server'; import { source } from '@/lib/source'; const server = createFromSource(source, { - // https://docs.orama.com/docs/orama-js/supported-languages - language: 'english', + localeMap: { + zh: { + language: 'english', + }, + }, }); export async function loader({ request }: Route.LoaderArgs) { diff --git a/packages/docs/app/lib/i18n.ts b/packages/docs/app/lib/i18n.ts new file mode 100644 index 0000000..a9f18b1 --- /dev/null +++ b/packages/docs/app/lib/i18n.ts @@ -0,0 +1,8 @@ +import { defineI18n } from 'fumadocs-core/i18n'; + +export const i18n = defineI18n({ + defaultLanguage: 'zh', + languages: ['zh', 'en'], + hideLocale: 'default-locale', + parser: 'dir', // 使用目录结构 (content/zh/*, content/en/*) +}); diff --git a/packages/docs/app/lib/layout.shared.tsx b/packages/docs/app/lib/layout.shared.tsx index af2b6f0..6e90ba0 100644 --- a/packages/docs/app/lib/layout.shared.tsx +++ b/packages/docs/app/lib/layout.shared.tsx @@ -1,9 +1,24 @@ import type { BaseLayoutProps } from 'fumadocs-ui/layouts/shared'; +import { i18n } from './i18n'; -export function baseOptions(): BaseLayoutProps { +export function baseOptions(locale: string): BaseLayoutProps { + // 默认语言(zh)不显示前缀,其他语言显示前缀 + const isDefaultLocale = locale === i18n.defaultLanguage; + const localePrefix = isDefaultLocale ? '' : `/${locale}`; + return { + i18n, nav: { - title: 'React Router', + title: 'DropOut', + url: localePrefix || '/', }, + githubUrl: 'https://github.com/HydroRoll-Team/DropOut', + links: [ + { + type: 'main', + text: locale === 'zh' ? '文档' : 'Documentation', + url: `${localePrefix}/docs`, + }, + ], }; } diff --git a/packages/docs/app/lib/source.ts b/packages/docs/app/lib/source.ts index 97cf767..bce9bf9 100644 --- a/packages/docs/app/lib/source.ts +++ b/packages/docs/app/lib/source.ts @@ -1,7 +1,12 @@ import { loader } from 'fumadocs-core/source'; import { docs } from 'fumadocs-mdx:collections/server'; +import { i18n } from './i18n'; export const source = loader({ source: docs.toFumadocsSource(), baseUrl: '/docs', + i18n, + // hideLocale: 'default-locale' 会自动生成正确的 URL: + // - 默认语言 (zh): /docs/getting-started + // - 其他语言 (en): /en/docs/getting-started }); diff --git a/packages/docs/app/root.tsx b/packages/docs/app/root.tsx index 08b8aa8..9032c80 100644 --- a/packages/docs/app/root.tsx +++ b/packages/docs/app/root.tsx @@ -1,15 +1,30 @@ import { isRouteErrorResponse, + Link, Links, Meta, Outlet, Scripts, ScrollRestoration, + useParams, } from 'react-router'; import { RootProvider } from 'fumadocs-ui/provider/react-router'; import type { Route } from './+types/root'; import './app.css'; +import { defineI18nUI } from 'fumadocs-ui/i18n'; +import { i18n } from './lib/i18n'; +const { provider } = defineI18nUI(i18n, { + translations: { + en: { + displayName: 'English', + }, + zh: { + displayName: '中文', + search: '查找文档', + }, + }, +}); export const links: Route.LinksFunction = () => [ { rel: 'preconnect', href: 'https://fonts.googleapis.com' }, { @@ -24,8 +39,10 @@ export const links: Route.LinksFunction = () => [ ]; export function Layout({ children }: { children: React.ReactNode }) { + const { lang = i18n.defaultLanguage } = useParams(); + return ( - <html lang="en" suppressHydrationWarning> + <html lang={lang} suppressHydrationWarning> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> @@ -33,7 +50,7 @@ export function Layout({ children }: { children: React.ReactNode }) { <Links /> </head> <body className="flex flex-col min-h-screen"> - <RootProvider>{children}</RootProvider> + <RootProvider i18n={provider(lang)}>{children}</RootProvider> <ScrollRestoration /> <Scripts /> </body> @@ -60,13 +77,27 @@ export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { } return ( - <main className="pt-16 p-4 w-full max-w-[1400px] mx-auto"> - <h1>{message}</h1> - <p>{details}</p> + <main className="flex-1 flex flex-col items-center p-4 mt-40 text-center"> + <h1 className="text-9xl font-black mb-4 bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-transparent opacity-50"> + {message} + </h1> + <p className="text-2xl font-semibold mb-2">{details}</p> + <p className="text-fd-muted-foreground mb-8 max-w-md"> + Sorry, we couldn't find the page you're looking for. It might have been moved or deleted. + </p> + <Link + to="/" + className="bg-blue-600 hover:bg-blue-700 text-white font-bold rounded-full px-8 py-3 shadow-lg shadow-blue-500/30 transition-all hover:scale-105 active:scale-95" + > + Return Home / 返回首页 + </Link> {stack && ( - <pre className="w-full p-4 overflow-x-auto"> - <code>{stack}</code> - </pre> + <div className="mt-12 w-full max-w-2xl text-left"> + <p className="text-xs font-mono text-fd-muted-foreground mb-2 uppercase tracking-widest">Error Stack</p> + <pre className="p-4 overflow-x-auto text-sm bg-fd-muted rounded-xl border border-fd-border font-mono whitespace-pre-wrap break-all shadow-inner"> + <code>{stack}</code> + </pre> + </div> )} </main> ); diff --git a/packages/docs/app/routes.ts b/packages/docs/app/routes.ts index 60dd630..2997ecf 100644 --- a/packages/docs/app/routes.ts +++ b/packages/docs/app/routes.ts @@ -1,7 +1,16 @@ -import { index, route, type RouteConfig } from '@react-router/dev/routes'; +import { route, type RouteConfig } from '@react-router/dev/routes'; export default [ - index('routes/home.tsx'), - route('docs/*', 'docs/page.tsx'), - route('api/search', 'docs/search.ts'), + // Home routes: / and /:lang + route(':lang?', 'routes/home.tsx', { id: 'home' }), + + // Docs routes: /docs/* and /:lang/docs/* + route(':lang?/docs', 'routes/docs.tsx', { id: 'docs' }), + route(':lang?/docs/*', 'docs/page.tsx', { id: 'docs-page' }), + + // API routes + route('api/search', 'docs/search.ts', { id: 'api-search' }), + + // Catch-all 404 + route('*', 'routes/not-found.tsx', { id: 'not-found' }), ] satisfies RouteConfig; diff --git a/packages/docs/app/routes/docs.tsx b/packages/docs/app/routes/docs.tsx new file mode 100644 index 0000000..5154d27 --- /dev/null +++ b/packages/docs/app/routes/docs.tsx @@ -0,0 +1,16 @@ +import type { Route } from './+types/docs'; +import { redirect } from 'react-router'; + +import { i18n } from '@/lib/i18n'; + +export function loader({ params }: Route.LoaderArgs) { + const lang = params.lang as string | undefined; + + // 如果没有语言参数或是默认语言,重定向到 /docs/getting-started + if (!lang || lang === i18n.defaultLanguage) { + return redirect('/docs/getting-started'); + } + + // 其他语言重定向到 /:lang/docs/getting-started + return redirect(`/${lang}/docs/getting-started`); +} diff --git a/packages/docs/app/routes/home.tsx b/packages/docs/app/routes/home.tsx index 7f03ba9..66b5333 100644 --- a/packages/docs/app/routes/home.tsx +++ b/packages/docs/app/routes/home.tsx @@ -1,29 +1,172 @@ import type { Route } from './+types/home'; import { HomeLayout } from 'fumadocs-ui/layouts/home'; -import { Link } from 'react-router'; import { baseOptions } from '@/lib/layout.shared'; +import { i18n } from '@/lib/i18n'; -export function meta({}: Route.MetaArgs) { +const texts = { + en: { + hero: { + title: 'DropOut Minecraft Launcher', + subtitle: 'Modern. Reproducible. Developer-Grade.', + description: 'Built with Tauri v2 and Rust for native performance and minimal resource usage', + start: 'Get Started', + features: 'Features', + }, + features: { + items: [ + { title: 'High Performance', desc: 'Built with Rust and Tauri for minimal resource usage and fast startup times' }, + { title: 'Modern UI', desc: 'Clean, distraction-free interface with Svelte 5 and Tailwind CSS 4' }, + { title: 'Secure Auth', desc: 'Microsoft OAuth 2.0 with device code flow and offline mode support' }, + { title: 'Mod Loaders', desc: 'Built-in support for Fabric and Forge with automatic version management' }, + { title: 'Java Management', desc: 'Auto-detection and integrated downloader for Adoptium JDK/JRE' }, + { title: 'Instance System', desc: 'Isolated game environments with independent configs and mods' }, + ] + }, + why: { + title: 'Why DropOut?', + items: [ + { q: 'Your instance worked yesterday but broke today?', a: '→ DropOut makes it traceable.' }, + { q: 'Sharing a modpack means zipping gigabytes?', a: '→ DropOut shares exact dependency manifests.' }, + { q: 'Java, loader, mods, configs drift out of sync?', a: '→ DropOut locks them together.' }, + ] + }, + cta: { + title: 'Ready to get started?', + desc: 'Check out the documentation to learn more about DropOut', + button: 'Read the Docs', + } + }, + zh: { + hero: { + title: 'DropOut Minecraft 启动器', + subtitle: '现代、可复现、开发者级', + description: '基于 Tauri v2 和 Rust 构建,拥有原生性能和极低的资源占用', + start: '开始使用', + features: '功能特性', + }, + features: { + items: [ + { title: '高性能', desc: '使用 Rust 和 Tauri 构建,资源占用最小,启动速度极快' }, + { title: '现代化界面', desc: '简洁、无干扰的界面,使用 Svelte 5 和 Tailwind CSS 4' }, + { title: '安全认证', desc: '支持微软 OAuth 2.0 设备代码流和离线模式' }, + { title: '模组支持', desc: '内置 Fabric 和 Forge 支持,自动管理版本' }, + { title: 'Java 管理', desc: '自动检测并集成 Adoptium JDK/JRE 下载器' }, + { title: '实例系统', desc: '独立的游戏环境,独立的配置和模组' }, + ] + }, + why: { + title: '为什么选择 DropOut?', + items: [ + { q: '你的实例昨天还能用,今天就坏了?', a: '→ DropOut 让它可追溯。' }, + { q: '分享模组包意味着打包数GB的文件?', a: '→ DropOut 分享精确的依赖清单。' }, + { q: 'Java、加载器、模组、配置不同步?', a: '→ DropOut 将它们锁定在一起。' }, + ] + }, + cta: { + title: '准备好开始了?', + desc: '查看文档以了解更多关于 DropOut 的信息', + button: '阅读文档', + } + } +}; + +export function meta({ params }: Route.MetaArgs) { return [ - { title: 'New React Router App' }, - { name: 'description', content: 'Welcome to React Router!' }, + { title: 'DropOut - Modern Minecraft Launcher' }, + { name: 'description', content: 'A modern, reproducible, and developer-grade Minecraft launcher built with Tauri v2 and Rust.' }, ]; } -export default function Home() { +export default function Home({ params }: Route.ComponentProps) { + const lang = (params.lang as 'en' | 'zh') || i18n.defaultLanguage; + const t = texts[lang]; + + // 默认语言(zh)不显示前缀,其他语言显示前缀 + const isDefaultLocale = lang === i18n.defaultLanguage; + const localePrefix = isDefaultLocale ? '' : `/${lang}`; + return ( - <HomeLayout {...baseOptions()}> - <div className="p-4 flex flex-col items-center justify-center text-center flex-1"> - <h1 className="text-xl font-bold mb-2">Fumadocs on React Router.</h1> - <p className="text-fd-muted-foreground mb-4"> - The truly flexible docs framework on React.js. - </p> - <Link - className="text-sm bg-fd-primary text-fd-primary-foreground rounded-full font-medium px-4 py-2.5" - to="/docs" - > - Open Docs - </Link> + <HomeLayout {...baseOptions(lang)}> + <div className="container max-w-6xl mx-auto px-4 py-16"> + {/* Hero Section */} + <div className="text-center mb-16"> + <h1 className="text-5xl font-bold mb-6 bg-gradient-to-r from-blue-600 to-cyan-500 bg-clip-text text-transparent"> + {t.hero.title} + </h1> + <p className="text-xl text-fd-muted-foreground mb-2"> + {t.hero.subtitle} + </p> + <p className="text-lg text-fd-muted-foreground max-w-2xl mx-auto mb-8"> + {t.hero.description} + </p> + <div className="flex gap-4 justify-center mb-12"> + <a + className="bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg px-6 py-3 transition-colors cursor-pointer" + href={`${localePrefix}/docs`} + > + {t.hero.start} + </a> + <a + className="bg-fd-secondary hover:bg-fd-secondary/80 text-fd-secondary-foreground font-semibold rounded-lg px-6 py-3 transition-colors cursor-pointer" + href={`${localePrefix}/docs/features`} + > + {t.hero.features} + </a> + </div> + </div> + + {/* Launcher Showcase */} + <div className="mb-16"> + <div className="rounded-xl overflow-hidden shadow-2xl border border-fd-border"> + <img + src="/image.png" + alt="DropOut Launcher Interface" + className="w-full h-auto" + /> + </div> + </div> + + {/* Features Grid */} + <div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6 mb-16"> + {t.features.items.map((item, i) => ( + <div key={i} className="p-6 rounded-lg border border-fd-border bg-fd-card"> + <h3 className="font-semibold text-lg mb-2">{item.title}</h3> + <p className="text-sm text-fd-muted-foreground"> + {item.desc} + </p> + </div> + ))} + </div> + + {/* Why DropOut Section */} + <div className="text-center mb-16"> + <h2 className="text-3xl font-bold mb-6">{t.why.title}</h2> + <div className="max-w-3xl mx-auto space-y-4 text-left"> + {t.why.items.map((item, i) => ( + <div key={i} className="p-4 rounded-lg bg-fd-muted/50"> + <p className="text-fd-foreground"> + <span className="font-semibold">{item.q}</span> + <br /> + <span className="text-fd-muted-foreground">{item.a}</span> + </p> + </div> + ))} + </div> + </div> + + {/* CTA Section */} + <div className="text-center py-12 px-6 rounded-xl bg-gradient-to-r from-blue-600/10 to-cyan-500/10 border border-blue-600/20"> + <h2 className="text-3xl font-bold mb-4">{t.cta.title}</h2> + <p className="text-lg text-fd-muted-foreground mb-6"> + {t.cta.desc} + </p> + <a + className="inline-block bg-blue-600 hover:bg-blue-700 text-white font-semibold rounded-lg px-8 py-3 transition-colors" + href={`${localePrefix}/docs/getting-started`} + > + {t.cta.button} + </a> + </div> </div> </HomeLayout> ); diff --git a/packages/docs/app/routes/not-found.tsx b/packages/docs/app/routes/not-found.tsx new file mode 100644 index 0000000..1d9e041 --- /dev/null +++ b/packages/docs/app/routes/not-found.tsx @@ -0,0 +1,7 @@ +export function loader() { + throw new Response('Not Found', { status: 404 }); +} + +export default function NotFound() { + return null; +} |