HEXO 开发笔记(8)自建主题:搜索与国际化
创建于 2026-06-19
更新于 2026-06-19
科技
hexo
主题开发
搜索
i18n
3728 字 · 约 13 分钟

前言

本篇聚焦 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 搜索

bash
1
2
# 构建时上传索引 npx hexo algolia
pug
1
2
3
4
5
6
7
8
// 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 适配花了不少功夫。主要挑战是搜索结果列表需要在不同内容宽度下正确显示 — 文章标题过长时截断、搜索高亮文字不溢出、分页控件在窄屏下折叠。

stylus
1
2
// source/css/_layout/search.styl — 100 行 // source/css/_layout/algolia.styl — 92 行

两个样式文件加起来 192 行,覆盖了搜索框、搜索结果、高亮文字、分页、空状态等所有交互状态。

1.4 本地搜索

作为 Algolia 的备用方案,本地搜索完全在客户端运行:

javascript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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 字段合并策略

yaml
1
2
3
4
5
6
search: field_merge_strategy: merge # merge(追加)| replace(替换) fields: - title - content - tags

merge 模式在默认字段基础上追加用户自定义字段,replace 模式完全替换。这个设计来自 hexo-generator-search 的思路,保留了扩展灵活性。

二、国际化(i18n)

2.1 一种强迫症式的设计偏好

国际化属于个人的一种强迫症式习惯 — 喜欢这种可配置化、可扩展的方案设计。Hexo 本身支持 i18n,有相关文档,所以积极引入。虽然博客主要是中文,但 i18n 体系让主题具备了国际化能力,未来如果有需要可以直接扩展。

2.2 语言文件结构

yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 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 双语日志

javascript
1
2
3
4
5
6
// 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 主题的架构,支持 TwikooValineGitment 三种。选择 Twikoo 的原因是看到其他站点在用,感觉好用就尝试往里加了。

3.2 动态注入

javascript
1
2
3
4
5
6
7
// 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 体系全面覆盖了所有用户可见文本,体现了可配置化的设计偏好。评论系统虽然功能完备但因合规原因关闭,这也算是个人博客的一个现实约束。下一篇(终篇)将介绍工程化实践和发布流程。

参考

本文作者: 有次元袋的 tiger
本文链接: https://www.superheaoz.top/2026/06/57042/
版权声明: 本站点所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 我的个人天地
手机扫码阅读