Files
blog/src/i18n/ui.ts
T
wait4xandGLM-5.3 938558ad4c feat(launch): 热力图与 webhook 发布闭环(T2/T3)
- T2 (op-artalk 车道):gitea heatmap 构建期拉取+fallback+空态三保险,
  53×7 灰阶格复用 tokens(亮暗自动),日界锚定 Asia/Shanghai,零 JS,
  zh/en 首页挂载;fallback 经死端点实测不破构建
- T3:webhook_receiver.py(HMAC 强制+main-only+构建锁)以 systemd
  --user 常驻 13021;gitea hook 注册(events=push, branch=main)
- 三个深坑:gitea 容器回宿主须走桥网关 IP 而非 127.0.0.1;gitea 28
  egress 拦私网须 ALLOWED_HOST_LIST(ALLOWLIST 是废弃名);systemd
  用户服务缺用户 PATH 致 build 静默失败(stamp 不更新即症状)

验证: 测试投递 202→构建→staging 戳更新(09:48:37 实测);
      dist 热力图 376 格真数据(9 次贡献)zh/en 双页

Spec: 2026-10-25_add-blog-launch
Co-authored-by: GLM-5.3
2026-10-25 09:49:19 +08:00

83 lines
5.6 KiB
TypeScript
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.
/**
* UI 双语字典:组件静态文案的单一事实源。
* 约定:key 稳定小写蛇形;zh 是源语言(先有),en 跟随;
* 车道协作:op-gitea 扩展本表并串接五组件(Footer/SearchDialog/gallery/moments/about)。
*/
export type Locale = 'zh' | 'en';
const dict = {
skip_to_content: { zh: '跳到正文', en: 'Skip to content' },
nav_posts: { zh: '文章', en: 'Posts' },
nav_gallery: { zh: '图库', en: 'Gallery' },
nav_about: { zh: '关于', en: 'About' },
search_label: { zh: '搜索', en: 'Search' },
search_placeholder: { zh: '搜索文章…(Esc 关闭)', en: 'Search posts… (Esc to close)' },
search_hint: { zh: '索引由 Pagefind 构建期生成 · 静态站零后端', en: 'Index built by Pagefind · zero-backend static site' },
footer_copyright: { zh: '©', en: '©' },
reading_time_min: { zh: '分钟', en: 'min' },
category_reading: { zh: '阅读', en: 'Reading' },
category_tech: { zh: '技术', en: 'Tech' },
category_photo: { zh: '照片', en: 'Photo' },
latest_photos: { zh: '最新影像', en: 'Latest photos' },
enter_gallery: { zh: '进入图库 →', en: 'Gallery →' },
open_source_projects: { zh: '开源 · Projects', en: 'Open Source · Projects' },
self_hosted: { zh: '自托管', en: 'self-hosted' },
prev_post: { zh: '上一篇', en: 'Previous' },
next_post: { zh: '下一篇', en: 'Next' },
ai_translated_note: {
zh: '本文由机器翻译自中文原文,欢迎指正。',
en: 'Machine-translated from the Chinese original. Corrections welcome.',
},
// ===== T2 车道(op-gitea):Footer/SearchDialog/gallery/moments/about 静态文案 =====
// footer_icp 是法定备案标识,英文页照抄不译
search_no_results: { zh: '无结果', en: 'No results' },
footer_icp: { zh: '陕ICP备2026014860号-1', en: '陕ICP备2026014860号-1' },
gallery_title: { zh: '图库', en: 'Gallery' },
gallery_filter_legend: { zh: '分组筛选', en: 'Filter by group' },
gallery_filter_all: { zh: '全部', en: 'All' },
gallery_filter_street: { zh: '街拍', en: 'Street' },
gallery_filter_landscape: { zh: '风景', en: 'Landscape' },
// {n}/{title}/{exif} 为占位符,调用点 .replace() 填值
gallery_count: { zh: '共 {n} 张 · EXIF 构建期自动提取', en: '{n} photos · EXIF extracted at build time' },
gallery_view_aria: { zh: '查看大图:{title}', en: 'View photo: {title}' },
gallery_view_aria_exif: { zh: '({exif})', en: ' ({exif})' },
gallery_lightbox_label: { zh: '照片查看', en: 'Photo viewer' },
gallery_lightbox_close: { zh: '关闭(Esc)', en: 'Close (Esc)' },
gallery_lightbox_prev: { zh: '上一张(←)', en: 'Previous (←)' },
gallery_lightbox_next: { zh: '下一张(→)', en: 'Next (→)' },
gallery_lightbox_original: { zh: '原图', en: 'Original' },
gallery_lightbox_download: { zh: '下载', en: 'Download' },
moments_title: { zh: '瞬间', en: 'Moments' },
moments_note: { zh: '零碎的记录:随手拍、一句话感想、开源动态。', en: 'Fragmented notes: casual shots, one-liners, and open-source updates.' },
about_title: { zh: '关于', en: 'About' },
about_tracks_title: { zh: '兴趣版图', en: 'Interests' },
about_track_reading_name: { zh: '阅读', en: 'Reading' },
about_track_reading_desc: { zh: '文学与戏剧为主,读到哪写到哪。', en: 'Mostly literature and drama; I write as I read.' },
about_track_tech_name: { zh: '技术', en: 'Tech' },
about_track_tech_desc: { zh: 'Linux、容器与自托管,把家里的小机器折腾成顺手的工具。', en: 'Linux, containers, and self-hosting — turning the little machines at home into handy tools.' },
about_track_photo_name: { zh: '摄影', en: 'Photography' },
about_track_photo_desc: { zh: '街拍与山野,随身一台无反,在等光的间隙按快门。', en: 'Street and landscapes with a mirrorless camera, pressing the shutter while waiting for the light.' },
about_build_aria: { zh: '本站构建说明', en: 'Site build notes' },
about_build_title: { zh: '本站如何构建', en: 'How this site is built' },
about_build_desc: {
zh: '页面由 Astro 静态生成,不含运行时框架 JS;内容住在自托管 gitea——本地编辑器与 gitea 网页写的是同一个 git 仓库,push 之后由 homelab 自动构建发布;评论走自托管 Artalk。页脚 built from 一行链到本次构建对应的 commit,构建过程透明可追溯。',
en: 'Pages are statically generated by Astro with no runtime framework JS. Content lives on a self-hosted gitea — my local editor and the gitea web UI write to the same git repository, and each push is built and published automatically by the homelab. Comments run on self-hosted Artalk. The built-from line in the footer links to the commit behind this build, keeping the process transparent and traceable.',
},
// ===== 热力图(Phase 4 T2 车道):文案全部页面层注入,组件保持 locale 无关 =====
heatmap_title: { zh: '开源活动', en: 'Open Source Activity' },
// {n} 为总贡献次数占位符,调用点 .replace() 填值
heatmap_summary: { zh: '最近一年 {n} 次贡献', en: '{n} contributions in the last year' },
heatmap_less: { zh: '少', en: 'Less' },
heatmap_more: { zh: '多', en: 'More' },
heatmap_note: { zh: '数据来自 gitea · 构建期生成', en: 'via gitea · generated at build time' },
} as const;
export type UiKey = keyof typeof dict;
/** 取串:locale 缺省 zh;未知 key 原样返回(编译期 tsc 已挡)。 */
export function t(key: UiKey, locale: Locale = 'zh'): string {
return dict[key][locale] ?? dict[key].zh;
}