diff --git a/_tmp_build.log b/_tmp_build.log new file mode 100644 index 0000000..8bf35fe --- /dev/null +++ b/_tmp_build.log @@ -0,0 +1,11 @@ + +> test@0.0.1 build +> astro build + +[safe-delete] 操作失败: ERROR D:\project\test\test\node_modules\.vite\deps: Error during a `trash` operation: Unknown { description: "Some operations were aborted" } + Stack trace: + at trashViaBinary (D:\apps\workbuddy\resources\app.asar.unpacked\cli\vendor\shim\genie-safe-delete.cjs:270:15) + at tryTrash (D:\apps\workbuddy\resources\app.asar.unpacked\cli\vendor\shim\genie-safe-delete.cjs:547:5) + at Object.wrappedPromisesRm [as rm] (D:\apps\workbuddy\resources\app.asar.unpacked\cli\vendor\shim\genie-safe-delete.cjs:766:15) + at async optimizeExplicitEnvironmentDeps (file:///D:/project/test/test/node_modules/vite/dist/node/chunks/node.js:31978:25) + at async Promise.all (index 1) diff --git a/astro.config.mjs b/astro.config.mjs index 1def40c..fe1dd1d 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -11,6 +11,13 @@ export default defineConfig({ vite: { plugins: [tailwindcss()] }, + i18n: { + defaultLocale: 'en', + locales: ['en', 'cn'], + routing: { + prefixDefaultLocale: false, // 英文在根路径 /shows,中文在 /cn/shows + }, + }, build: { // 示例:在构建过程中生 成`page.html` 而不是 `page/index.html`。 format: 'file' diff --git a/src/components/pages/About.astro b/src/components/pages/About.astro new file mode 100644 index 0000000..3f4ac53 --- /dev/null +++ b/src/components/pages/About.astro @@ -0,0 +1,6 @@ +--- +// 页面主体(与语言无关的内容),由根页面与 [locale] 页面共用 +import ComingSoon from "../../components/ComingSoon.astro" +--- + + diff --git a/src/components/pages/Contact.astro b/src/components/pages/Contact.astro new file mode 100644 index 0000000..cf241e7 --- /dev/null +++ b/src/components/pages/Contact.astro @@ -0,0 +1,6 @@ +--- +// 页面主体(与语言无关的内容),由根页面与 [locale] 页面共用 +import ComingSoon from "../../components/ComingSoon.astro" +--- + + diff --git a/src/components/pages/LatestProjects.astro b/src/components/pages/LatestProjects.astro new file mode 100644 index 0000000..abd52e2 --- /dev/null +++ b/src/components/pages/LatestProjects.astro @@ -0,0 +1,6 @@ +--- +// 页面主体(与语言无关的内容),由根页面与 [locale] 页面共用 +import ComingSoon from "../../components/ComingSoon.astro" +--- + + diff --git a/src/components/pages/Login.astro b/src/components/pages/Login.astro new file mode 100644 index 0000000..f27463e --- /dev/null +++ b/src/components/pages/Login.astro @@ -0,0 +1,6 @@ +--- +// 页面主体(与语言无关的内容),由根页面与 [locale] 页面共用 +import ComingSoon from "../../components/ComingSoon.astro" +--- + + diff --git a/src/components/pages/Portfolio.astro b/src/components/pages/Portfolio.astro new file mode 100644 index 0000000..e96a16a --- /dev/null +++ b/src/components/pages/Portfolio.astro @@ -0,0 +1,6 @@ +--- +// 页面主体(与语言无关的内容),由根页面与 [locale] 页面共用 +import ComingSoon from "../../components/ComingSoon.astro" +--- + + diff --git a/src/components/pages/Register.astro b/src/components/pages/Register.astro new file mode 100644 index 0000000..da59b7c --- /dev/null +++ b/src/components/pages/Register.astro @@ -0,0 +1,6 @@ +--- +// 页面主体(与语言无关的内容),由根页面与 [locale] 页面共用 +import ComingSoon from "../../components/ComingSoon.astro" +--- + + diff --git a/src/components/pages/article.astro b/src/components/pages/article.astro new file mode 100644 index 0000000..875075c --- /dev/null +++ b/src/components/pages/article.astro @@ -0,0 +1,460 @@ +--- + +// 示例数据:字段严格对齐后端 BrandRunway 模型 + 图片集接口 +const article = { + id: 1, + title: "Maison Lumen — Paper Light", + description: + "巴黎,2026 秋冬。Maison Lumen 以“Paper Light(纸光)”为题,将薄纱、宣纸与压褶工艺叠合成一种近乎透明的体积感。\n\n系列延续了工作室对“轻”的执念:肩线被刻意放低,廓形在行走中随风摆动,像被光穿透的纸。色彩仅保留骨白、炭灰与一抹雾蓝。\n\n这不是关于廓形的宣言,而是关于 material 的克制——用最少的手段,制造最大的呼吸感。", + created_at: 1711948800, + updated_at: 1711948800, + year: 2026, + source_url: "https://example.com/runway/maison-lumen-paper-light", + brand_id: 12, + brand_name: "Maison Lumen", + images: [ + { id: 1, image: "https://picsum.photos/seed/art1/1200/1500", name: "Look 01", sort_order: 1 }, + { id: 2, image: "https://picsum.photos/seed/art2/1200/1500", name: "Look 02", sort_order: 2 }, + { id: 3, image: "https://picsum.photos/seed/art3/1200/1500", name: "Look 03", sort_order: 3 }, + { id: 4, image: "https://picsum.photos/seed/art4/1200/1500", name: "Look 04", sort_order: 4 }, + ], +} + +const fmt = (ts: number) => { + const d = new Date(ts * 1000) + const y = d.getFullYear() + const m = String(d.getMonth() + 1).padStart(2, "0") + const day = String(d.getDate()).padStart(2, "0") + return `${y}.${m}.${day}` +} + +const body = article.description.split("\n\n") +--- + +
+ +
+ ← Index + Runway · {article.brand_name} +
+ + +

+ {article.title} +

+ + +
+ {fmt(article.created_at)} + {article.year} + Source ↗ +
+ + +
+
+ Report +
+
+ {body.map((p) =>

{p}

)} +
+
+ + +
+
+
+ Gallery +
+ +
+ + + +
+ + +
+ + + + + + + + + diff --git a/src/components/pages/articles.astro b/src/components/pages/articles.astro new file mode 100644 index 0000000..37ae6ef --- /dev/null +++ b/src/components/pages/articles.astro @@ -0,0 +1,471 @@ +--- +import { getReports, fmtDate } from "../../lib/data" + +// 文章展示创意布局页:复用你现有的 Layout 外壳。 +// 数据由数据层 getReports() 统一从后端公开接口拉取(见 src/lib/data.ts); +// 后端不可达时回落到内置示例,非对称创意布局结构完全不变。 + +const reports = await getReports(30) +const fmt = fmtDate +--- + +
+ +
+
+ Runway Archive + SS23 — AW26 +
+

The Reports

+
+

+ 走秀趋势报告档案库。支持网格 / 杂志 / 时间轴三种创意视图自由切换,彩色配图由后端接口动态渲染。 +

+ ← Index +
+
+ + +
+
+ + + +
+ {reports.length} Reports +
+ + +
+ + +
+ + + + + + +
+
+ + + +
+ + + +
+ + + + diff --git a/src/components/pages/brands.astro b/src/components/pages/brands.astro new file mode 100644 index 0000000..4d9aee8 --- /dev/null +++ b/src/components/pages/brands.astro @@ -0,0 +1,255 @@ +--- + +// 品牌索引页(HBX 风格:每个字母一行,品牌名横向展开) +// - 数据:一次 SSR 拉完全部精选集合(?size=300&only_with_articles=1&featured=1), +// 前端按 name 首字母分桶渲染;198 个品牌数据量小,无需懒加载。 +// - 风格:极简编辑风(黑白 + monospace + 细线)。 +// · 顶部单行 BRANDS 标题 + 总数 +// · sticky A-Z 字母条(点击平滑滚动) +// · 每个字母一行:左侧字母标签 + 右侧该字母下品牌名横向铺成多列 +// · 无副标题、无数字 badge、无大字标题、无悬停黑底、无搜索框 +// 构建期 SSR 拉数据用 API_SSR(容器内部后端地址,如 http://backend:8090); +// 运行期浏览器走同源 /api(由 nginx 反代),PUBLIC_API_BASE 置空即可。 +const API = (import.meta.env.API_SSR || import.meta.env.PUBLIC_API_BASE || "http://localhost:8090").replace(/\/$/, "") + +interface Brand { + id: number + name: string + show_name: string + article_count: number +} + +let brands: Brand[] = [] +let loadError = false +try { + const res = await fetch(`${API}/api/public/brands?size=300&only_with_articles=1&featured=1`) + if (res.ok) { + const j = await res.json() + brands = (j?.data ?? []) as Brand[] + } else { + loadError = true + } +} catch { + loadError = true +} + +const LETTERS = Array.from("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + +// 主显示名:拉丁/英文名优先(用于 A-Z 归类与展示),无拉丁名时回退中文 +const primaryName = (b: Brand): string => { + const name = (b.name || "").trim() + const show = (b.show_name || "").trim() + if (/^[A-Za-z]/.test(name)) return name + if (show) { + const segs = show.split("/") + const lat = segs.find((s) => /^[A-Za-z]/.test(s.trim())) + if (lat) return lat.trim() + } + return name || show || "Unnamed" +} + +// 按首字母归类到 A-Z(中文/符号开头的品牌不再单独成桶,直接忽略) +type Bucket = Record +const buckets: Bucket = {} +LETTERS.forEach((l) => (buckets[l] = [])) + +for (const b of brands) { + const p = primaryName(b) + const first = (p[0] || "").toUpperCase() + if (first >= "A" && first <= "Z") buckets[first].push(b) +} + +// 按字母顺序输出行 +const orderedColumns: { key: string; label: string; items: Brand[] }[] = [] +LETTERS.forEach((l) => { + if (buckets[l].length > 0) orderedColumns.push({ key: l, label: l, items: buckets[l] }) +}) + +const total = orderedColumns.reduce((s, c) => s + c.items.length, 0) +--- + +
+ +
+

+ Brands + {total.toLocaleString()} +

+
+ + + + + +
+ {loadError ? ( +

Failed to load brands. Please try again later.

+ ) : total === 0 ? ( +

No brands indexed yet.

+ ) : ( +
+ {orderedColumns.map((col) => ( +
+

{col.label}

+ +
+ ))} +
+ )} +
+
+ + + + diff --git a/src/i18n/config.ts b/src/i18n/config.ts new file mode 100644 index 0000000..cea96cf --- /dev/null +++ b/src/i18n/config.ts @@ -0,0 +1,36 @@ +// i18n 核心配置:语言列表、默认语言、URL 前缀规则 +// 改这里即可增减语言;新增语言记得同步 src/i18n/dictionary.ts 里的每个 key(单文件聚合多语言)。 + +/** 默认语言(无前缀时使用的语言,如 /shows)。 */ +export const DEFAULT_LOCALE = 'en' as const; + +/** 支持的语言列表。URL 前缀用的就是这些值,例如 /cn/shows 的 `cn`。 */ +export const LOCALES = ['en', 'cn'] as const; +export type Locale = (typeof LOCALES)[number]; + +/** + * 默认语言是否带 URL 前缀: + * - false → 英文在根路径(/shows),中文在 /cn/shows(本项目采用) + * - true → 所有语言都带前缀(/en/shows、/cn/shows) + */ +export const PREFIX_DEFAULT_LOCALE = false; + +/** 语言显示名(用于语言切换器等 UI)。 */ +export const LOCALE_NAMES: Record = { + en: 'English', + cn: '中文', +}; + +/** 浏览器 Accept-Language / navigator.language → 本项目支持的语言。 */ +export const BROWSER_LOCALE_MAP: Record = { + 'zh': 'cn', + 'zh-cn': 'cn', + 'zh-tw': 'cn', + 'zh-hk': 'cn', + 'en': 'en', +}; + +/** 判断一个路径段是不是已支持的语言前缀。 */ +export function isLocaleSegment(seg: string | undefined): seg is Locale { + return !!seg && (LOCALES as readonly string[]).includes(seg); +} diff --git a/src/i18n/dictionary.ts b/src/i18n/dictionary.ts new file mode 100644 index 0000000..4175b15 --- /dev/null +++ b/src/i18n/dictionary.ts @@ -0,0 +1,56 @@ +import { DEFAULT_LOCALE, type Locale } from './config'; + +/** + * 单文件翻译字典。 + * + * 设计(按你的要求): + * - key 就是「英文原文」,直接当文案用,无需额外维护英文副本; + * - value 只存「非默认语言」的翻译(本项目默认语言是 en,所以只存 cn 等); + * - 英文环境调用 t(key) 直接返回 key 本身; + * - 其它语言从 value 里取对应翻译,取不到就回退英文(开发环境会告警,方便发现漏翻)。 + * + * 新增一条:直接加一行 `'英文原文': { cn: '中文' }` 即可; + * 当前是英文时根本不需要在字典里登记,写了 t('Nice day!') 就能渲染 "Nice day!"。 + */ + +// 除默认语言外的所有语言(如 'cn')。加新语言(ja 等)时这里会自动包含。 +type OtherLocale = Exclude; +type TranslationEntry = Partial>; + +export const dict: Record = { + Portfolio: { cn: '作品集' }, + 'All Shows': { cn: '全部走秀' }, + 'Latest Projects': { cn: '最新项目' }, + About: { cn: '关于' }, + 'Contact Us': { cn: '联系我们' }, + Login: { cn: '登录' }, + Signup: { cn: '注册' }, + Contact: { cn: '联系' }, + 'Back to top': { cn: '回到顶部' }, + '© 2026 PORTFOLIO STUDIO. ALL RIGHTS RESERVED.': { cn: '© 2026 PORTFOLIO STUDIO. 保留所有权利。' }, + Previous: { cn: '上一组' }, + Next: { cn: '下一组' }, +}; + +/** 把 {name} 形式的占位符替换成参数值。 */ +function interpolate(text: string, params?: Record): string { + if (!params) return text; + return text.replace(/\{(\w+)\}/g, (_, k) => (k in params ? String(params[k]) : `{${k}}`)); +} + +/** + * 取翻译: + * - 当前是默认语言(en)→ 直接返回 key(英文原文); + * - 其它语言 → 优先取字典里的翻译,缺失则回退英文并返回 key(不会白屏)。 + */ +export function translate(key: string, locale: Locale, params?: Record): string { + if (locale === DEFAULT_LOCALE) return interpolate(key, params); + + const localized = dict[key]?.[locale as OtherLocale]; + if (localized) return interpolate(localized, params); + + if (import.meta.env.DEV) { + console.warn(`[i18n] 缺少 "${locale}" 翻译:"${key}",已回退英文`); + } + return interpolate(key, params); +} diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts new file mode 100644 index 0000000..0148e69 --- /dev/null +++ b/src/i18n/utils.ts @@ -0,0 +1,49 @@ +import { + DEFAULT_LOCALE, + LOCALES, + PREFIX_DEFAULT_LOCALE, + isLocaleSegment, + type Locale, +} from './config'; +import { translate } from './dictionary'; + +/** + * 从当前 URL 解析语言: + * - 路径首段若是支持的语言前缀(如 /cn/shows → cn)则用之; + * - 否则回退到默认语言(en)。 + */ +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}`; + }; +} + +/** + * 取翻译函数 t():key 即英文原文,按当前语言自动匹配翻译。 + * - 英文环境:t('All Shows') 直接返回 'All Shows'(key 本身) + * - 中文环境:t('All Shows') 返回字典里的 '全部走秀'(取不到则回退英文) + * - 支持 {name} 形式的插值参数。 + */ +export function useTranslations(locale: Locale) { + function t(key: string, params?: Record): string { + return translate(key, locale, params); + } + return { t, locale }; +} + +export { LOCALES, DEFAULT_LOCALE }; +export type { Locale }; diff --git a/src/layouts/Layout.astro b/src/layouts/Layout.astro index ef5620b..15556a0 100644 --- a/src/layouts/Layout.astro +++ b/src/layouts/Layout.astro @@ -1,5 +1,7 @@ --- import '../styles/global.css' +import { getLocaleFromUrl, useTranslations, useTranslatedPath } from '../i18n/utils' +import { LOCALES, LOCALE_NAMES, isLocaleSegment } from '../i18n/config' const { title = 'Portfolio Studio', @@ -8,6 +10,17 @@ const { activeThemePalette = null, } = Astro.props; +const locale = getLocaleFromUrl(Astro.url) +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' }, @@ -17,7 +30,7 @@ const categories: { label: string; value: PortfolioCategory | 'Trending Colors' ]; --- - + @@ -25,25 +38,55 @@ const categories: { label: string; value: PortfolioCategory | 'Trending Colors' {title} + +
@@ -196,26 +239,30 @@ const categories: { label: string; value: PortfolioCategory | 'Trending Colors'
+ +
+ {t('© 2026 PORTFOLIO STUDIO. ALL RIGHTS RESERVED.')} +
diff --git a/src/pages/about.astro b/src/pages/about.astro index d46787b..9e0bc50 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -1,8 +1,8 @@ --- import Layout from "../layouts/Layout.astro" -import ComingSoon from "../components/ComingSoon.astro" +import About from "../components/pages/About.astro" --- - + diff --git a/src/pages/article.astro b/src/pages/article.astro index e27aabe..88152b6 100644 --- a/src/pages/article.astro +++ b/src/pages/article.astro @@ -1,463 +1,8 @@ --- import Layout from "../layouts/Layout.astro" - -// 示例数据:字段严格对齐后端 BrandRunway 模型 + 图片集接口 -const article = { - id: 1, - title: "Maison Lumen — Paper Light", - description: - "巴黎,2026 秋冬。Maison Lumen 以“Paper Light(纸光)”为题,将薄纱、宣纸与压褶工艺叠合成一种近乎透明的体积感。\n\n系列延续了工作室对“轻”的执念:肩线被刻意放低,廓形在行走中随风摆动,像被光穿透的纸。色彩仅保留骨白、炭灰与一抹雾蓝。\n\n这不是关于廓形的宣言,而是关于 material 的克制——用最少的手段,制造最大的呼吸感。", - created_at: 1711948800, - updated_at: 1711948800, - year: 2026, - source_url: "https://example.com/runway/maison-lumen-paper-light", - brand_id: 12, - brand_name: "Maison Lumen", - images: [ - { id: 1, image: "https://picsum.photos/seed/art1/1200/1500", name: "Look 01", sort_order: 1 }, - { id: 2, image: "https://picsum.photos/seed/art2/1200/1500", name: "Look 02", sort_order: 2 }, - { id: 3, image: "https://picsum.photos/seed/art3/1200/1500", name: "Look 03", sort_order: 3 }, - { id: 4, image: "https://picsum.photos/seed/art4/1200/1500", name: "Look 04", sort_order: 4 }, - ], -} - -const fmt = (ts: number) => { - const d = new Date(ts * 1000) - const y = d.getFullYear() - const m = String(d.getMonth() + 1).padStart(2, "0") - const day = String(d.getDate()).padStart(2, "0") - return `${y}.${m}.${day}` -} - -const body = article.description.split("\n\n") +import Article from "../components/pages/article.astro" --- - -
- -
- ← Index - Runway · {article.brand_name} -
- - -

- {article.title} -

- - -
- {fmt(article.created_at)} - {article.year} - Source ↗ -
- - -
-
- Report -
-
- {body.map((p) =>

{p}

)} -
-
- - -
-
-
- Gallery -
- -
- - - -
- - -
- - - + +
- - - - - - diff --git a/src/pages/articles.astro b/src/pages/articles.astro index 071601e..fb6e566 100644 --- a/src/pages/articles.astro +++ b/src/pages/articles.astro @@ -1,474 +1,8 @@ --- import Layout from "../layouts/Layout.astro" -import { getReports, fmtDate } from "../lib/data" - -// 文章展示创意布局页:复用你现有的 Layout 外壳。 -// 数据由数据层 getReports() 统一从后端公开接口拉取(见 src/lib/data.ts); -// 后端不可达时回落到内置示例,非对称创意布局结构完全不变。 - -const reports = await getReports(30) -const fmt = fmtDate +import Articles from "../components/pages/articles.astro" --- -
- -
-
- Runway Archive - SS23 — AW26 -
-

The Reports

-
-

- 走秀趋势报告档案库。支持网格 / 杂志 / 时间轴三种创意视图自由切换,彩色配图由后端接口动态渲染。 -

- ← Index -
-
- - -
-
- - - -
- {reports.length} Reports -
- - -
- - -
- - - - - - -
-
- - - -
- - - -
+
- - - - diff --git a/src/pages/brands.astro b/src/pages/brands.astro index 1344999..01fbdbd 100644 --- a/src/pages/brands.astro +++ b/src/pages/brands.astro @@ -1,258 +1,8 @@ --- import Layout from "../layouts/Layout.astro" - -// 品牌索引页(HBX 风格:每个字母一行,品牌名横向展开) -// - 数据:一次 SSR 拉完全部精选集合(?size=300&only_with_articles=1&featured=1), -// 前端按 name 首字母分桶渲染;198 个品牌数据量小,无需懒加载。 -// - 风格:极简编辑风(黑白 + monospace + 细线)。 -// · 顶部单行 BRANDS 标题 + 总数 -// · sticky A-Z 字母条(点击平滑滚动) -// · 每个字母一行:左侧字母标签 + 右侧该字母下品牌名横向铺成多列 -// · 无副标题、无数字 badge、无大字标题、无悬停黑底、无搜索框 -// 构建期 SSR 拉数据用 API_SSR(容器内部后端地址,如 http://backend:8090); -// 运行期浏览器走同源 /api(由 nginx 反代),PUBLIC_API_BASE 置空即可。 -const API = (import.meta.env.API_SSR || import.meta.env.PUBLIC_API_BASE || "http://localhost:8090").replace(/\/$/, "") - -interface Brand { - id: number - name: string - show_name: string - article_count: number -} - -let brands: Brand[] = [] -let loadError = false -try { - const res = await fetch(`${API}/api/public/brands?size=300&only_with_articles=1&featured=1`) - if (res.ok) { - const j = await res.json() - brands = (j?.data ?? []) as Brand[] - } else { - loadError = true - } -} catch { - loadError = true -} - -const LETTERS = Array.from("ABCDEFGHIJKLMNOPQRSTUVWXYZ") - -// 主显示名:拉丁/英文名优先(用于 A-Z 归类与展示),无拉丁名时回退中文 -const primaryName = (b: Brand): string => { - const name = (b.name || "").trim() - const show = (b.show_name || "").trim() - if (/^[A-Za-z]/.test(name)) return name - if (show) { - const segs = show.split("/") - const lat = segs.find((s) => /^[A-Za-z]/.test(s.trim())) - if (lat) return lat.trim() - } - return name || show || "Unnamed" -} - -// 按首字母归类到 A-Z(中文/符号开头的品牌不再单独成桶,直接忽略) -type Bucket = Record -const buckets: Bucket = {} -LETTERS.forEach((l) => (buckets[l] = [])) - -for (const b of brands) { - const p = primaryName(b) - const first = (p[0] || "").toUpperCase() - if (first >= "A" && first <= "Z") buckets[first].push(b) -} - -// 按字母顺序输出行 -const orderedColumns: { key: string; label: string; items: Brand[] }[] = [] -LETTERS.forEach((l) => { - if (buckets[l].length > 0) orderedColumns.push({ key: l, label: l, items: buckets[l] }) -}) - -const total = orderedColumns.reduce((s, c) => s + c.items.length, 0) +import Brands from "../components/pages/brands.astro" --- - -
- -
-

- Brands - {total.toLocaleString()} -

-
- - - - - -
- {loadError ? ( -

Failed to load brands. Please try again later.

- ) : total === 0 ? ( -

No brands indexed yet.

- ) : ( -
- {orderedColumns.map((col) => ( -
-

{col.label}

- -
- ))} -
- )} -
-
+ + - - - - diff --git a/src/pages/cn/index.astro b/src/pages/cn/index.astro new file mode 100644 index 0000000..9b96226 --- /dev/null +++ b/src/pages/cn/index.astro @@ -0,0 +1,344 @@ +--- +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, + }) +} +--- + + +
+ +
+
    +
  • + 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/pages/contact.astro b/src/pages/contact.astro index e1ad78b..578c606 100644 --- a/src/pages/contact.astro +++ b/src/pages/contact.astro @@ -1,8 +1,8 @@ --- import Layout from "../layouts/Layout.astro" -import ComingSoon from "../components/ComingSoon.astro" +import Contact from "../components/pages/Contact.astro" --- - + diff --git a/src/pages/index.astro b/src/pages/index.astro index 8a45c7c..066dea4 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -2,10 +2,15 @@ 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; @@ -74,13 +79,7 @@ for (const s of shows) {
-
-
-

RUNWAY ARCHIVE

-

走秀档案 · 精选最新上架的秀场

-
- 全部走秀 → -
+
Hot Brand
{ @@ -129,8 +128,8 @@ for (const s of shows) {
-
Previous
-
Next
+
{t('Previous')}
+
{t('Next')}
diff --git a/src/pages/latest-projects.astro b/src/pages/latest-projects.astro index 03baa65..c8baca9 100644 --- a/src/pages/latest-projects.astro +++ b/src/pages/latest-projects.astro @@ -1,8 +1,8 @@ --- import Layout from "../layouts/Layout.astro" -import ComingSoon from "../components/ComingSoon.astro" +import LatestProjects from "../components/pages/LatestProjects.astro" --- - + diff --git a/src/pages/login.astro b/src/pages/login.astro index c4e24f1..21415a8 100644 --- a/src/pages/login.astro +++ b/src/pages/login.astro @@ -1,8 +1,8 @@ --- import Layout from "../layouts/Layout.astro" -import ComingSoon from "../components/ComingSoon.astro" +import Login from "../components/pages/Login.astro" --- - + diff --git a/src/pages/portfolio.astro b/src/pages/portfolio.astro index c122097..7f6162e 100644 --- a/src/pages/portfolio.astro +++ b/src/pages/portfolio.astro @@ -1,8 +1,8 @@ --- import Layout from "../layouts/Layout.astro" -import ComingSoon from "../components/ComingSoon.astro" +import Portfolio from "../components/pages/Portfolio.astro" --- - + diff --git a/src/pages/register.astro b/src/pages/register.astro index d181355..10bd979 100644 --- a/src/pages/register.astro +++ b/src/pages/register.astro @@ -1,8 +1,8 @@ --- import Layout from "../layouts/Layout.astro" -import ComingSoon from "../components/ComingSoon.astro" +import Register from "../components/pages/Register.astro" --- - +