From efd28d59e79dedd5b62bf028bfdfaac2de934b18 Mon Sep 17 00:00:00 2001 From: toom1996 Date: Sun, 16 Aug 2026 20:51:04 +0800 Subject: [PATCH] update --- .workbuddy/memory/2026-08-16.md | 50 +++++ astro.config.mjs | 4 +- src/components/Index.astro | 340 +++++++++++++++++++++++++++++++ src/i18n/utils.ts | 36 +--- src/layouts/Layout.astro | 146 +++++++------- src/lib/data.ts | 1 - src/pages/about.astro | 8 - src/pages/cn/about.astro | 8 + src/pages/cn/index.astro | 342 +------------------------------- src/pages/en/about.astro | 8 + src/pages/en/index.astro | 8 + src/pages/index.astro | 341 +------------------------------ src/styles/global.css | 2 + 13 files changed, 512 insertions(+), 782 deletions(-) create mode 100644 .workbuddy/memory/2026-08-16.md create mode 100644 src/components/Index.astro delete mode 100644 src/pages/about.astro create mode 100644 src/pages/cn/about.astro create mode 100644 src/pages/en/about.astro create mode 100644 src/pages/en/index.astro diff --git a/.workbuddy/memory/2026-08-16.md b/.workbuddy/memory/2026-08-16.md new file mode 100644 index 0000000..e71d34f --- /dev/null +++ b/.workbuddy/memory/2026-08-16.md @@ -0,0 +1,50 @@ +# 2026-08-16 + +## i18n 取语言方式重构 +- 项目 i18n 已配置(defaultLocale: 'en', locales: ['en','cn'], prefixDefaultLocale: false)。 +- 将 `.astro` 页面里 `getLocaleFromUrl(Astro.url)` 改为 `Astro.currentLocale`(Astro 路由解析,更稳健),并补类型兜底 `(Astro.currentLocale as Locale) ?? DEFAULT_LOCALE`。 +- 涉及文件:src/layouts/Layout.astro、src/pages/index.astro、src/pages/cn/index.astro。 +- src/i18n/utils.ts 的 `getLocaleFromUrl` 纯函数保留(供 .ts 模块或解析任意 URL 使用)。 +## 结论 +- `Astro.currentLocale` 可替代 `getLocaleFromUrl(Astro.url)`,但仅限 .astro 文件;纯 TS / 解析非当前页 URL 仍需自定义函数。 + +## Astro 浏览器语言检测(重要,修正此前误解) +- Astro 确实提供 `Astro.preferredLocale` / `Astro.preferredLocaleList`,但它是**服务端按请求**结合 `Accept-Language` 头计算的,仅 SSR 运行时有效。 +- **本项目是静态构建 + nginx 托管**:build 阶段无访客、无 Accept-Language 头,运行时也无 Node 进程重算,故 `preferredLocale` 在静态产物里是 undefined、`preferredLocaleList` 回退为全部 i18n.locales,**无法在运行时检测访客语言**。 +- 客户端 `navigator.language` 脚本仍是当前静态部署下唯一运行时生效的方案。 +- 若要改用 `Astro.preferredLocale` + `Astro.redirect()`:必须迁移到 SSR(加 adapter、output:'server'、nginx 后跑 Node),且需在 i18n.locales 为 `cn` 补 `codes: ['zh','zh-CN','zh-TW','zh-HK']` 才能把浏览器 `zh-CN` 匹配到 `cn`(当前 `locales:['en','cn']` 裸码匹配不到)。 + +## Bug 修复:header 语言切换器不显示 +- 根因:`src/layouts/Layout.astro` 中语言切换器(`LOCALES.map`)原是 `#auth-slot` 的子节点;页脚 auth 脚本 `fetchMe()` 未登录返回 null → `render(null)` 用 `slot.innerHTML` 整块重写成 Login/Signup,把语言切换器一并抹掉。任何未登录访客都触发,故语言选项永远看不见。 +- 修复:(1) 将语言切换器移出 `#auth-slot`,独立为 `#lang-switch` 兄弟节点,脚本重写不再波及;(2) 未登录时不再硬编码 `/login` 重写 `#auth-slot`,保留 SSR 的带语言前缀 Login/Signup,顺带修好 /cn 页登录链接丢前缀问题。 +- 教训:header 中「语言切换器」与「登录态 UI」必须分属不同容器,避免被 auth 脚本的 innerHTML 重写误伤。 + +## 语言切换器改为下拉菜单 +- 将 header `#lang-switch` 从「English / 中文」并排链接改为 Alpine 下拉(`x-data="{open:false}"` + `@click.outside` + `@keydown.escape.window`,绝对定位 `right-0`、`z-50`,带 chevron 旋转)。选中项 `font-bold`,其余 `opacity-60`。 +- 在 `src/styles/global.css` 补 `[x-cloak]{display:none!important}`(原仅 shows/index 各自 ` + + \ No newline at end of file diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts index 0148e69..09c1e52 100644 --- a/src/i18n/utils.ts +++ b/src/i18n/utils.ts @@ -1,35 +1,19 @@ -import { - DEFAULT_LOCALE, - LOCALES, - PREFIX_DEFAULT_LOCALE, - isLocaleSegment, - type Locale, -} from './config'; +import { getLocaleByPath } from 'astro:i18n'; +import { DEFAULT_LOCALE, LOCALES, type Locale } from './config'; import { translate } from './dictionary'; /** - * 从当前 URL 解析语言: - * - 路径首段若是支持的语言前缀(如 /cn/shows → cn)则用之; - * - 否则回退到默认语言(en)。 + * 从 URL 解析语言,委托 Astro 内置的 astro:i18n.getLocaleByPath。 + * 无前缀(默认语言)路径会解析回 DEFAULT_LOCALE。 */ export function getLocaleFromUrl(url: URL | string): Locale { const pathname = typeof url === 'string' ? url : url.pathname; - const seg = pathname.split('/').filter(Boolean)[0]; - return isLocaleSegment(seg) ? seg : DEFAULT_LOCALE; -} - -/** - * 生成带语言前缀的链接。 - * - en(默认且 PREFIX_DEFAULT_LOCALE=false)→ /shows - * - cn → /cn/shows - * 第二个参数可指定目标语言,方便做语言切换器。 - */ -export function useTranslatedPath(locale: Locale) { - return (path: string, targetLocale: Locale = locale): string => { - const clean = path.startsWith('/') ? path : `/${path}`; - if (targetLocale === DEFAULT_LOCALE && !PREFIX_DEFAULT_LOCALE) return clean; - return `/${targetLocale}${clean}`; - }; + try { + const locale = getLocaleByPath(pathname); + return (LOCALES as readonly string[]).includes(locale) ? (locale as Locale) : DEFAULT_LOCALE; + } catch { + return DEFAULT_LOCALE; + } } /** diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index 15556a0..b67769b 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,6 +1,7 @@ --- import '../styles/global.css' -import { getLocaleFromUrl, useTranslations, useTranslatedPath } from '../i18n/utils' +import { getRelativeLocaleUrl } from 'astro:i18n' +import { useTranslations, DEFAULT_LOCALE, type Locale } from '../i18n/utils' import { LOCALES, LOCALE_NAMES, isLocaleSegment } from '../i18n/config' const { @@ -10,24 +11,19 @@ const { activeThemePalette = null, } = Astro.props; -const locale = getLocaleFromUrl(Astro.url) +const locale = (Astro.currentLocale as Locale) ?? DEFAULT_LOCALE const { t } = useTranslations(locale) -const translatePath = useTranslatedPath(locale) -// 当前路径去掉语言前缀,用于语言切换器拼出「同页面、另一语言」的链接 -const _segs = Astro.url.pathname.split('/').filter(Boolean); -const _stripped = _segs.length > 0 && isLocaleSegment(_segs[0]) - ? '/' + _segs.slice(1).join('/') - : Astro.url.pathname; -const currentPath = _stripped === '' ? '/' : _stripped; -const categories: { label: string; value: PortfolioCategory | 'Trending Colors' }[] = [ - { label: 'x', value: 'x' }, - { label: 'Backstage', value: 'Backstage' }, - { label: 'Events', value: 'Events' }, - { label: 'Street', value: 'Street' }, - { label: 'Trending Colors', value: 'Trending Colors' }, -]; +// 当前路径去掉语言前缀与尾随 .html(format:'file' 产物如 cn.html / shows.html), +// 用于语言切换器拼出「同页面、另一语言」的链接(逻辑路径,不带 .html) +// 例:/cn.html → / ,/cn/shows.html → /shows ,/shows.html → /shows +let _segs = Astro.url.pathname.split('/').filter(Boolean); +if (_segs[0] && isLocaleSegment(_segs[0])) _segs = _segs.slice(1); +const _last = _segs[_segs.length - 1]; +if (_last && _last.endsWith('.html')) _segs = _segs.slice(0, -1); +const currentPath = _segs.length === 0 ? '/' : '/' + _segs.join('/'); + --- @@ -61,31 +57,70 @@ const categories: { label: string; value: PortfolioCategory | 'Trending Colors' @@ -98,13 +133,10 @@ const categories: { label: string; value: PortfolioCategory | 'Trending Colors' import { getUser, fetchMe, clearSession } from "../lib/auth.ts" const slot = document.getElementById("auth-slot") function render(user) { - if (user) { - slot.innerHTML = `Hi, ${user.username}` - const btn = slot.querySelector("#logout-btn") - if (btn) btn.addEventListener("click", () => { clearSession(); location.reload() }) - } else { - slot.innerHTML = `LoginSignup` - } + if (!user) return // 未登录:保留 SSR 渲染的 Login/Signup(已带语言前缀,无需重写) + slot.innerHTML = `Hi, ${user.username}` + const btn = slot.querySelector("#logout-btn") + if (btn) btn.addEventListener("click", () => { clearSession(); location.reload() }) } const cached = getUser() if (cached) render(cached) @@ -140,30 +172,6 @@ const categories: { label: string; value: PortfolioCategory | 'Trending Colors' - {/* Column 2: Quick Navigation */} -
- - Categories - - -
- {/* Column 3: Color Studio & Actions */}
@@ -239,13 +247,13 @@ const categories: { label: string; value: PortfolioCategory | 'Trending Colors'
{t('About')} {t('Contact')} diff --git a/src/lib/data.ts b/src/lib/data.ts index f2c7d5e..8a01d6a 100644 --- a/src/lib/data.ts +++ b/src/lib/data.ts @@ -186,7 +186,6 @@ export function loadArticlesWithImages(size = 40): Promise { if (!API_BASE) return [] try { const res = await fetch(`${API_BASE}/api/public/articles?page=1&size=${size}&with_images=5`) - console.log(res) if (!res.ok) return [] const json: any = await res.json() const items: RawArticle[] = json?.data ?? [] diff --git a/src/pages/about.astro b/src/pages/about.astro deleted file mode 100644 index 9e0bc50..0000000 --- a/src/pages/about.astro +++ /dev/null @@ -1,8 +0,0 @@ ---- -import Layout from "../layouts/Layout.astro" -import About from "../components/pages/About.astro" ---- - - - - diff --git a/src/pages/cn/about.astro b/src/pages/cn/about.astro new file mode 100644 index 0000000..a38f791 --- /dev/null +++ b/src/pages/cn/about.astro @@ -0,0 +1,8 @@ +--- +import Layout from "../../layouts/Layout.astro" +import About from "../../components/pages/About.astro" +--- + + + + diff --git a/src/pages/cn/index.astro b/src/pages/cn/index.astro index 9b96226..90c247e 100644 --- a/src/pages/cn/index.astro +++ b/src/pages/cn/index.astro @@ -1,344 +1,8 @@ --- -import Layout from "../../layouts/Layout.astro" -import ComingSoon from "../../components/ComingSoon.astro" -import { getShows, seriesLabel } from "../../lib/data" -import { getLocaleFromUrl, useTranslations } from "../../i18n/utils" - -// SSR 预取走秀数据 -const shows = await getShows(20) - -// 国际化:根据 URL 前缀(/cn/...)取当前语言与翻译函数 -const locale = getLocaleFromUrl(Astro.url) -const { t } = useTranslations(locale) - -type GalleryItem = { - src: string; - brand: string; - articleId: number; - year: number; - title: string; - collection_type?: string -} - -const galleryItems: GalleryItem[] = [] -for (const s of shows) { - if (!s.img) continue - galleryItems.push({ - src: s.img, - brand: s.brand, - articleId: s.id, - year: s.year, - title: s.title, - collection_type: s.collection_type, - }) -} +import Layout from '../../layouts/Layout.astro' +import Index from '../../components/Index.astro' --- -
- - - - -
- -
-
Hot Brand
- - - { - galleryItems.length > 0 && ( -
-
- -
-
{t('Previous')}
-
{t('Next')}
-
-
-
- ) - } -
- - -
- -
- - -
- -
- - -
- -
-
-
+ - - - - \ No newline at end of file diff --git a/src/pages/en/about.astro b/src/pages/en/about.astro new file mode 100644 index 0000000..a38f791 --- /dev/null +++ b/src/pages/en/about.astro @@ -0,0 +1,8 @@ +--- +import Layout from "../../layouts/Layout.astro" +import About from "../../components/pages/About.astro" +--- + + + + diff --git a/src/pages/en/index.astro b/src/pages/en/index.astro new file mode 100644 index 0000000..90c247e --- /dev/null +++ b/src/pages/en/index.astro @@ -0,0 +1,8 @@ +--- +import Layout from '../../layouts/Layout.astro' +import Index from '../../components/Index.astro' +--- + + + + diff --git a/src/pages/index.astro b/src/pages/index.astro index 066dea4..00cd9f6 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -1,344 +1,11 @@ --- -import Layout from "../layouts/Layout.astro" -import ComingSoon from "../components/ComingSoon.astro" -import { getShows, seriesLabel } from "../lib/data" -import { getLocaleFromUrl, useTranslations } from "../i18n/utils" -// SSR 预取走秀数据 -const shows = await getShows(20) +// 应该跳转到 /en -// 国际化:根据 URL 前缀(/cn/...)取当前语言与翻译函数 -const locale = getLocaleFromUrl(Astro.url) -const { t } = useTranslations(locale) - -type GalleryItem = { - src: string; - brand: string; - articleId: number; - year: number; - title: string; - collection_type?: string -} - -const galleryItems: GalleryItem[] = [] -for (const s of shows) { - if (!s.img) continue - galleryItems.push({ - src: s.img, - brand: s.brand, - articleId: s.id, - year: s.year, - title: s.title, - collection_type: s.collection_type, - }) -} +import Layout from '../layouts/Layout.astro' +import Index from '../components/Index.astro' --- -
- -
-
    -
  • - A - Live -
  • -
  • - B - Soon -
  • -
  • - C - Soon -
  • -
  • - D - Soon -
  • - E -
-
- - -
- -
-
Hot Brand
- - - { - galleryItems.length > 0 && ( -
-
- -
-
{t('Previous')}
-
{t('Next')}
-
-
-
- ) - } -
- - -
- -
- - -
- -
- - -
- -
-
-
+
- - - - \ No newline at end of file diff --git a/src/styles/global.css b/src/styles/global.css index f1d8c73..f441018 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1 +1,3 @@ @import "tailwindcss"; + +[x-cloak] { display: none !important; }