Files
pangolin/web/website/src/components/Header.jsx
T
wangjia f0d574bc44 feat(website/i18n): 6 语(加 日/韩/俄/西)+ 默认英文
- strings.ts: [zh,en] 二元组 → Record<Lang,string> 六语对象(157 条,ja/ko/ru/es
  据英文翻译,无红线词,scan-redline 过);createT 回退英文;PRICES 补 4 语(复用
  USD 价)
- 路由:默认英文移到 /(原 zh);中文挪 /zh/;新增 /ja//ko//ru//es/;删冗余 /en/
- Site.astro:html lang / canonical / hreflang(6+x-default)/ og:locale 全部改
  为按语言查表(HTML_LANG/OG_LOCALE/langPath)
- Header.jsx:langseg 2 项 → 原生 select 6 语下拉(横排会挤);+ .langsel 样式
- astro build 通过,6 页生成;/ 英文、/zh/ 中文、/ja/ 日语标题均已本地化

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-07 13:29:35 +08:00

98 lines
3.4 KiB
React
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Header.jsx — 交互式顶栏(React island@astrojs/react 渲染 + 水合)。
* 迁自 design/ui_kits/website/index.html 的 <header> + site.js 的菜单/滚动毛玻璃逻辑。
* 语言切换由原型的 JS 文本替换改为「路由跳转」(zh=/, en=/en/),单显不并排(铁律 6)。
*/
import { useEffect, useState } from 'react';
import { Download, Menu } from 'lucide-react';
import { SITE } from '../config/site';
function Mark() {
return (
<svg width="30" height="30" viewBox="0 0 96 96" fill="none" aria-hidden="true">
<g transform="translate(96,0) scale(-1,1)">
<path
d="M16 59 Q17 51 26 50 Q34 31 45 30 Q55 30 62 37 Q69 41 75 45 Q85 49 89 44 Q92 50 84 52 Q75 53 67 53 Q66 62 60 62 L56 62 Q54 55 49 55 Q47 62 37 62 L33 62 Q31 56 26 56 Q20 59 16 59 Z"
fill="#B96A3D"
/>
<g stroke="#FAF3ED" strokeWidth="2.4" strokeLinecap="round" strokeLinejoin="round" fill="none" opacity="0.85">
<path d="M26 50 Q34 43 42 50" />
<path d="M36 48 Q44 41 52 48" />
<path d="M46 48 Q54 41 62 49" />
</g>
<circle cx="23" cy="55" r="2.2" fill="#FAF3ED" />
</g>
</svg>
);
}
export default function Header({ lang = 'zh', t = {} }) {
const [open, setOpen] = useState(false);
const [scrolled, setScrolled] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 8);
onScroll();
window.addEventListener('scroll', onScroll, { passive: true });
return () => window.removeEventListener('scroll', onScroll);
}, []);
const nav = [
['#product', t.product],
['#pricing', t.pricing],
['#download', t.download],
['#docs', t.docs],
];
// 6 语言:默认 en=/,其余 /<lang>/。段控横排会挤,用原生下拉。
const langs = [
['en', 'English'], ['zh', '中文'], ['ja', '日本語'],
['ko', '한국어'], ['ru', 'Русский'], ['es', 'Español'],
];
const langHref = (l) => (l === 'en' ? '/' : `/${l}/`);
return (
<header class={`hdr${scrolled ? ' scrolled' : ''}`}>
<div class="wrap row">
<a class="brand" href="#top">
<Mark />
<span class="nm">穿山甲</span>
</a>
<nav class="nav">
{nav.map(([href, label]) => (
<a key={href} href={href}>{label}</a>
))}
</nav>
<div class="right">
<select
class="langsel"
value={lang}
onChange={(e) => { window.location.href = langHref(e.target.value); }}
aria-label="Language"
>
{langs.map(([code, label]) => (
<option key={code} value={code}>{label}</option>
))}
</select>
<a class="linklogin" href={SITE.usercenter}>{t.login}</a>
<a class="btn btn-primary" href="#download">
<Download />
<span>{t.get}</span>
</a>
<button class="menu-btn" onClick={() => setOpen((v) => !v)} aria-label="menu" aria-expanded={open}>
<Menu />
</button>
</div>
</div>
<div class={`mnav${open ? ' open' : ''}`} id="mnav">
{nav.map(([href, label]) => (
<a key={href} href={href} onClick={() => setOpen(false)}>{label}</a>
))}
<a class="btn btn-primary" href="#signup" onClick={() => setOpen(false)}>
<span>{t.suBtn}</span>
</a>
</div>
</header>
);
}