diff --git a/apps/frontend/src/components/Header.tsx b/apps/frontend/src/components/Header.tsx index 1e24b22..cf0e471 100644 --- a/apps/frontend/src/components/Header.tsx +++ b/apps/frontend/src/components/Header.tsx @@ -1,15 +1,11 @@ import { Github } from 'lucide-react' import { LocaleSwitcher } from '@/components/LocaleSwitcher' import { ThemeToggle } from '@/components/ThemeToggle' +import { getTranslations } from '@/i18n' import { localizePath } from '@/lib/routes' -const translations = { - en: { submit: 'Submit' }, - zh: { submit: '投稿' }, -} as const - export function Header({ locale }: { locale: 'en' | 'zh' }) { - const t = translations[locale] + const t = getTranslations(locale) return (
@@ -19,6 +15,12 @@ export function Header({ locale }: { locale: 'en' | 'zh' }) {
+ + {t.card.tags} + - {t.submit} + {t.nav.submit}
diff --git a/apps/frontend/src/components/MarkdownArticlePage.astro b/apps/frontend/src/components/MarkdownArticlePage.astro index ea61d96..93ddbb5 100644 --- a/apps/frontend/src/components/MarkdownArticlePage.astro +++ b/apps/frontend/src/components/MarkdownArticlePage.astro @@ -12,12 +12,13 @@ interface Props { const { locale, title, description, content } = Astro.props const htmlContent = await renderMarkdown(content, locale) +const dividerLabel = locale === 'zh' ? '内容' : 'Content' ---
-
-
+
+

{title}

@@ -31,8 +32,16 @@ const htmlContent = await renderMarkdown(content, locale)
+
+ + + {dividerLabel} + + +
+
-
+
diff --git a/apps/frontend/src/components/ThemeToggle.tsx b/apps/frontend/src/components/ThemeToggle.tsx index 5b7e541..25f872d 100644 --- a/apps/frontend/src/components/ThemeToggle.tsx +++ b/apps/frontend/src/components/ThemeToggle.tsx @@ -1,80 +1,62 @@ -import { Check, Moon, Sun } from 'lucide-react' -import { useCallback, useEffect, useState } from 'react' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, -} from '@/components/ui/dropdown-menu' +import { Moon, Sun, SunMoon } from 'lucide-react' +import { useCallback, useEffect, useMemo, useState } from 'react' +import { getTranslations } from '@/i18n' -type Theme = 'light' | 'dark' +type ThemeMode = 'light' | 'dark' | 'system' const STORAGE_KEY = 'bkio-theme' -function getSystemTheme(): Theme { - return window.matchMedia('(prefers-color-scheme: dark)').matches ? - 'dark' : - 'light' -} - -const themeOptions: { - value: Theme - icon: typeof Sun - en: string - zh: string -}[] = [ - { value: 'light', icon: Sun, en: 'Light', zh: '浅色' }, - { value: 'dark', icon: Moon, en: 'Dark', zh: '深色' }, -] +const themeOrder: ThemeMode[] = ['dark', 'light', 'system'] export function ThemeToggle({ locale }: { locale: 'en' | 'zh' }) { - const [theme, setThemeState] = useState(() => { - if (typeof window === 'undefined') - return 'light' + const t = getTranslations(locale) + const [theme, setThemeState] = useState('system') + + useEffect(() => { const saved = localStorage.getItem(STORAGE_KEY) - if (saved === 'light' || saved === 'dark') - return saved - return getSystemTheme() - }) + if (saved === 'light' || saved === 'dark' || saved === 'system') + setThemeState(saved) + }, []) useEffect(() => { - document.documentElement.classList.toggle('dark', theme === 'dark') + const media = window.matchMedia('(prefers-color-scheme: dark)') + const applyTheme = () => { + const resolvedTheme = theme === 'system' ? (media.matches ? 'dark' : 'light') : theme + document.documentElement.classList.toggle('dark', resolvedTheme === 'dark') + } + + applyTheme() localStorage.setItem(STORAGE_KEY, theme) + media.addEventListener('change', applyTheme) + return () => media.removeEventListener('change', applyTheme) + }, [theme]) + + const nextTheme = useMemo(() => { + const index = themeOrder.indexOf(theme) + return themeOrder[(index + 1) % themeOrder.length] }, [theme]) - const setTheme = useCallback((mode: Theme) => { - setThemeState(mode) + const currentLabel = t.theme[theme] + const nextLabel = t.theme[nextTheme] + + const setTheme = useCallback(() => { + setThemeState((current) => { + const index = themeOrder.indexOf(current) + return themeOrder[(index + 1) % themeOrder.length] + }) }, []) + const Icon = theme === 'dark' ? Moon : theme === 'light' ? Sun : SunMoon + return ( - - - {theme === 'dark' ? - ( - - ) : - ( - - )} - - - {themeOptions.map((opt) => { - const Icon = opt.icon - return ( - setTheme(opt.value)} - > - - {opt[locale]} - {theme === opt.value && } - - ) - })} - - + ) } diff --git a/apps/frontend/src/components/ToolDetailPage.astro b/apps/frontend/src/components/ToolDetailPage.astro index c358b99..9de5297 100644 --- a/apps/frontend/src/components/ToolDetailPage.astro +++ b/apps/frontend/src/components/ToolDetailPage.astro @@ -91,8 +91,8 @@ return null
-
-
+
+

{txt(meta.title, locale)}

@@ -149,16 +149,16 @@ return null )}
-
+
-
+
{sidebarDocs.length > 1 && (