前言
本篇聚焦 DoraTiger 主题的搜索系统和国际化(i18n)体系。搜索方面涵盖 Algolia 搜索和本地搜索两种方案;国际化方面介绍语言文件结构、占位符机制和全组件覆盖。此外还涉及评论系统的选型与停用原因。
一、搜索系统
1.1 从 Fan 到 DoraTiger 的搜索演进
搜索功能的演进和主题本身的历史紧密相关。最早在 Fan 主题上就遇到过 Algolia 适配出问题的情况 — 那也是我给 Fan 贡献代码的原因之一。后来在 DoraTiger 重构时,彻底升级了 Algolia 依赖版本(从 instantsearch.js v2 升级到 v4),基于新 API 全面重写了搜索模块。
本地搜索(local-search)则是作为 fallback 方案加入的 — 就像 PV 统计有 counter 服务和 localStorage 两种方案一样,搜索也需要一个不依赖外部服务的兜底。当 Algolia 配额用完或服务不可用时,本地搜索可以无缝接管。
1.2 Algolia 搜索
bash12# 构建时上传索引 npx hexo algolia
pug12345678// layout/_include/header/algolia.pug // instantsearch.js v4 + algoliasearch lite const search = instantsearch({ indexName: '#{algoliaConfig.index_id}', searchClient: algoliasearch('#{algoliaConfig.app_id}', '#{algoliaConfig.api_key}'), }); search.addWidget(instantsearch.widgets.searchBox({ container: '#search-box' })); search.addWidget(instantsearch.widgets.hits({ container: '#hits' }));
1.3 搜索框的适配
搜索框的 UI 适配花了不少功夫。主要挑战是搜索结果列表需要在不同内容宽度下正确显示 — 文章标题过长时截断、搜索高亮文字不溢出、分页控件在窄屏下折叠。
stylus12// source/css/_layout/search.styl — 100 行 // source/css/_layout/algolia.styl — 92 行
两个样式文件加起来 192 行,覆盖了搜索框、搜索结果、高亮文字、分页、空状态等所有交互状态。
1.4 本地搜索
作为 Algolia 的备用方案,本地搜索完全在客户端运行:
javascript1234567891011121314151617181920212223// source/js/utils/localSearch.js class LocalSearch { loadIndex() { // 懒加载:首次搜索时才 fetch JSON 索引 fetch(indexPath) .then(r => r.json()) .then(data => { this.index = data.items; this.state = "ready"; }); } search(query) { // 200ms 防抖 + 子串匹配 return this.index.filter(item => { const text = [item.title, item.excerpt, item.content, ...item.tags, ...item.categories].join(" ").toLowerCase(); return text.includes(query.toLowerCase()); }).slice(0, this.perPage); } highlight(text, query) { const regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "ig"); return text.replace(regex, m => `<mark>${m}</mark>`); } }
1.5 字段合并策略
yaml123456search: field_merge_strategy: merge # merge(追加)| replace(替换) fields: - title - content - tags
merge 模式在默认字段基础上追加用户自定义字段,replace 模式完全替换。这个设计来自 hexo-generator-search 的思路,保留了扩展灵活性。
二、国际化(i18n)
2.1 一种强迫症式的设计偏好
国际化属于个人的一种强迫症式习惯 — 喜欢这种可配置化、可扩展的方案设计。Hexo 本身支持 i18n,有相关文档,所以积极引入。虽然博客主要是中文,但 i18n 体系让主题具备了国际化能力,未来如果有需要可以直接扩展。
2.2 语言文件结构
yaml1234567891011121314151617# languages/zh-Hans.yml home: read_more: "阅读全文" archives: count: zero: "暂无文章" # _p() 复数:零 other: "目前共计 %d 篇文章" # _p() 复数:其他 footer: statistics: site_uv: "本站总访客数 {} 人" # JS 运行时插值 page_pv: "本文总访问量 {} 次" copy: success: "复制成功" error: "复制错误"
2.3 三种占位符
| 占位符 | 用途 | 使用场景 |
|---|---|---|
%d |
_p() 复数形式 |
Pug 模板 {{ archives.count }} |
{} |
JS 运行时插值 | 统计数字显示 |
${name} |
搜索结果模板 | 搜索组件 |
2.4 覆盖范围
i18n 体系在主题中是全面覆盖的 — 导航菜单、页面标题、文章元信息(创建时间/更新时间)、版权模板、复制按钮状态、搜索占位符和空结果提示、评论占位符、重定向提示、统计格式字符串等。所有用户可见的文本都通过 _p() 或 __() 函数引用,没有硬编码的中文字符串。
2.5 双语日志
javascript123456// scripts/utils/log.js function logInfo(key, ...args) { const lang = hexo.theme.i18n.languages[0]; const msg = lang.startsWith('zh') ? zhMessages[key] : enMessages[key]; console.log(msg, ...args); }
三、评论系统
3.1 选型与继承
评论系统继承自 Fan 主题的架构,支持 Twikoo、Valine、Gitment 三种。选择 Twikoo 的原因是看到其他站点在用,感觉好用就尝试往里加了。
3.2 动态注入
javascript1234567// scripts/injectors/lib/injector-comments.js switch (commentType) { case "twikoo": return `<script>new Twikoo({ envId: '${envId}' })</script>`; case "valine": return `<script>new Valine({ appId: '${appId}' })</script>`; }
3.3 为什么关闭了评论
目前评论系统是关闭状态。原因是网站备案是个人网页性质,没有交互属性,索性就关了。
之前还考虑过一个更复杂的方案 — 结合 GitHub Pages 做双栈部署,国外 IP 访问路由到 Pages 上并开启评论功能。但最后嫌麻烦放弃了。
四、总结
搜索和国际化是提升博客可用性的关键功能。Algolia 搜索从 Fan 时代的问题到 DoraTiger 的全面重写,本地搜索作为兜底方案保证了可用性。i18n 体系全面覆盖了所有用户可见文本,体现了可配置化的设计偏好。评论系统虽然功能完备但因合规原因关闭,这也算是个人博客的一个现实约束。下一篇(终篇)将介绍工程化实践和发布流程。

