diff --git a/src/components/shell/LangSwitcher.tsx b/src/components/shell/LangSwitcher.tsx index 0124c6f..9e22093 100644 --- a/src/components/shell/LangSwitcher.tsx +++ b/src/components/shell/LangSwitcher.tsx @@ -3,7 +3,7 @@ import { LOCALES, LOCALE_LABEL, LOCALE_NAME, localizePath, stripLocale, type Lan // Public sections that exist in every locale. Other paths (about, settings…) fall // back to the locale home when switching, so the switcher never lands on a 404. -const LOCALIZED_PREFIXES = ['/tools/', '/category/']; +const LOCALIZED_PREFIXES = ['/tools/', '/category/', '/about']; /** The URL for the current page in `lang` — computed fresh from the live location. */ function targetFor(lang: Lang): string { diff --git a/src/components/shell/ShellIsland.tsx b/src/components/shell/ShellIsland.tsx index ca51219..94ee37d 100644 --- a/src/components/shell/ShellIsland.tsx +++ b/src/components/shell/ShellIsland.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState, type ReactNode } from 'react'; import { Search, Github, Info, ExternalLink, Settings, MoreVertical } from 'lucide-react'; import { ThemeToggle } from './ThemeToggle'; import { LangSwitcher } from './LangSwitcher'; @@ -7,6 +7,7 @@ import { Modal } from '@/components/ui/Modal'; import { initTheme } from '@/stores/theme.store'; import { isTauri } from '@/services/platform'; import { REPO_URL } from '@/config'; +import type { Lang } from '@/i18n/config'; /** Opens the command palette from a click (works without a keyboard). */ export function openSearch() { @@ -18,18 +19,56 @@ const iconBtn = const menuItem = 'flex w-full items-center gap-2.5 px-3 py-2.5 text-left text-sm font-bold hover:bg-accent hover:text-accent-foreground'; +// Header UI strings, per language (the header persists across navigations, so the +// language is detected from the URL client-side rather than passed as a prop). +const S: Record = { + en: { + searchAria: 'Search tools', searchPre: 'Press', searchPost: 'to search', + about: 'About', contribute: 'Contribute on GitHub', contribMenu: 'Contribute', more: 'More', settings: 'Settings', + modalTitle: 'Contribute', + modalBody: ( + <> +

GoodWebTools is open source. Every tool runs entirely in your browser using modern web APIs and WebAssembly — nothing is uploaded.

+

Got an idea for a tool that could work fully client-side? Or found a bug? Please open an issue, or fork the repo and send a pull request — contributions and requests are very welcome.

+ + ), + }, + id: { + searchAria: 'Cari tool', searchPre: 'Tekan', searchPost: 'untuk mencari', + about: 'Tentang', contribute: 'Kontribusi di GitHub', contribMenu: 'Kontribusi', more: 'Lainnya', settings: 'Pengaturan', + modalTitle: 'Kontribusi', + modalBody: ( + <> +

GoodWebTools bersifat sumber terbuka. Setiap tool berjalan sepenuhnya di browser Anda menggunakan API web modern dan WebAssembly — tidak ada yang diunggah.

+

Punya ide tool yang bisa berjalan sepenuhnya di sisi klien? Atau menemukan bug? Silakan buka issue, atau fork repo dan kirim pull request — kontribusi dan permintaan sangat kami hargai.

+ + ), + }, +}; + export function ShellIsland() { const [modal, setModal] = useState(null); const [menuOpen, setMenuOpen] = useState(false); + const [lang, setLang] = useState('en'); // Settings only apply to the desktop app; hide the nav link on the web. // Starts false so SSR and the first client render match, then reveals on // desktop after mount (avoids a hydration mismatch). const [isDesktop, setIsDesktop] = useState(false); const menuRef = useRef(null); + const s = S[lang]; useEffect(() => { initTheme(); setIsDesktop(isTauri()); + // Detect language from the URL, and keep it current across view transitions. + const syncLang = () => setLang(/^\/id(\/|$)/.test(location.pathname) ? 'id' : 'en'); + syncLang(); + document.addEventListener('astro:page-load', syncLang); + return () => document.removeEventListener('astro:page-load', syncLang); }, []); // Close the mobile overflow menu on outside click, Escape, or navigation. @@ -62,43 +101,43 @@ export function ShellIsland() {
{/* Secondary actions — inline on ≥sm, in an overflow menu on mobile. */}
{isDesktop && ( - + )} - + -
- {menuOpen && (
{isDesktop && ( - Settings + {s.settings} )} - About + {s.about}
)} @@ -114,17 +153,9 @@ export function ShellIsland() { {modal === 'github' && ( - setModal(null)}> + setModal(null)}>
-

- GoodWebTools is open source. Every tool runs entirely - in your browser using modern web APIs and WebAssembly — nothing is uploaded. -

-

- Got an idea for a tool that could work fully client-side? Or found a bug? Please{' '} - open an issue, or fork the - repo and send a pull request — contributions and requests are very welcome. -

+ {s.modalBody} -