缓存与 i18n
多级缓存
GoPress 把缓存当作架构基础而不是优化手段:
| 层级 | 介质 | 用途 |
|---|---|---|
| L1 | 进程内 LRU | 单实例热数据,零网络延迟 |
| L2 | Redis | 跨实例共享、容量大 |
| 整页缓存 | L1+L2 复用 | 命中时 < 1ms 直接返回完整 HTML |
| 数据库 | PostgreSQL | 内容、设置、菜单、媒体和扩展数据的最终事实来源 |
缓存 Key 自动包含语言维度 — 多语言场景下不同语言的页面缓存独立,不会互相污染。
按标签批量失效 — 内容更新、菜单调整、设置变更触发对应标签清理,避免脏数据。
后台手动清理 — 缓存管理页提供清理全部缓存、整页缓存和片段缓存的操作。
Redis 可选 — 配置中删除 [redis] 段即降级为纯内存缓存,单实例部署完全可用。
按站点隔离的 Redis 命名空间 — Redis Key 统一以 gopress:<namespace>: 开头,清理全部缓存、按前缀失效都只作用于当前站点。命名空间来自 [redis].namespace,未配置时由数据库连接与表前缀派生,因此多个站点可以安全共用同一个 Redis DB。详见配置。
核心 i18n 系统
GoPress 的国际化能力由 Core 层 提供,多语言插件负责数据库覆盖和管理后台,两者通过接口解耦:
没有插件提供数据库翻译时,Core 仍是一套完整的单语言 CMS;任何翻译 key 缺失都会继续回退,不会让页面无法渲染。
┌─────────────────────────────────────────────────────────────────────┐
│ Core 层 │
│ │
│ core/i18n/ │
│ ├── Manager # 分层消息 + 不可变 Bundle 快照 + Localizer │
│ ├── T(c, key) # 模板函数:翻译 UI 字符串 │
│ ├── TranslateOption() # 翻译单个主题设置项 │
│ └── TranslateSettings() # 批量翻译所有可翻译设置项 │
│ │
│ core/option/ │
│ ├── RegisterTranslatable(key, section, label) # 注册可翻译设置键 │
│ ├── IsTranslatable(key) # 判断是否可翻译 │
│ └── AllTranslatableKeys() # 返回所有注册键 │
└────────────────────────────────┬────────────────────────────────────┘
│ AddMessages() / LoadLocalesFS() / LoadThemeLocales()
▼
┌─────────────────────────────────────────────────────────────────────┐
│ 数据来源(优先级从高到低) │
│ │
│ 1. 覆盖层:DB StringTranslation ← multilang 插件经 AddMessages() │
│ domain="theme" → UI 字符串覆盖 │
│ domain="option" → 主题设置翻译(_opt. 前缀,防 ID 碰撞) │
│ 2. 扩展层:插件 locale 目录 ← LoadLocalesFS(),ID 带命名空间 │
│ 3. 主题层:当前主题 locale 文件 ← themes/xxx/locales/en.json 等 │
│ 4. message ID 原样返回 ← 兜底 │
└─────────────────────────────────────────────────────────────────────┘
并发与主题切换 — go-i18n 的 Bundle 不允许在读取时修改,因此 Manager 不直接改写正在使用的 Bundle:任意一层变化后,都会用全部三层重新构建一个新 Bundle 并原子发布。已经创建的 Localizer 继续使用创建时的快照,正在处理的请求不受影响;新请求使用新快照。主题激活时整层替换主题层,切换到没有某个 key(或没有 locales/)的主题后,不会再回退显示上一个主题的文案;数据库覆盖层则跨主题保留。Bundle() 返回的是只读快照,扩展应通过 AddMessages 或 LoadLocalesFS 修改翻译。
UI 字符串翻译
主题在 locales/ 目录提供 JSON 翻译文件,模板中 {{T .Ctx "welcome"}} 直接使用。multilang 插件后台「字符串翻译管理」允许在 DB 中覆盖或新增翻译条目。
主题设置翻译
主题在 translatable.go 中调用 option.RegisterTranslatable() 声明可翻译的设置键(如 hero 标题、about 描述)。Core 的 TranslateSettings() 在渲染时自动翻译 settings map 中的可翻译键。multilang 插件后台「主题设置翻译」提供按分组的翻译编辑界面。
// 主题声明可翻译设置(themes/modern-company/translatable.go)
func registerTranslatableOptions() {
option.RegisterTranslatable("home_hero_title", "hero", "Hero 标题")
option.RegisterTranslatable("home_about_title", "about", "关于标题")
// ... 可翻译设置键覆盖首页 hero / about 区块、数据统计、产品/服务展示、CTA 等区块全文案
}
// Core 自动翻译(渲染时,theme handler 中调用)
func (p *PageData) TranslateSettings(c *gin.Context, mgr *i18n.Manager) {
p.Settings = mgr.TranslateSettings(c, p.Settings, option.IsTranslatable, option.AllTranslatableKeys())
}
模板可用函数
下面以主题声明的 product 内容类型 URL 为例,实际路径由当前内容类型的 rewrite_slug 决定。
// 翻译 UI 字符串
{{T .Ctx "welcome"}}
// 获取当前语言代码
{{currentLang .Ctx}}
// 生成带语言前缀的 URL
{{langPrefixURL .Ctx "/products/hepa-filters"}}
// 生成内容类型归档和详情 URL,读取 Rewrite 注册表
{{archiveURL "product"}}
{{contentURL . "product"}}
// 生成规范 taxonomy 归档 URL
{{taxonomyURL "category" .Slug}}
语言请求作用域
多语言插件可以通过通用内容与 Taxonomy Scope API添加 内容及 Term 的语言查询条件,通过菜单 Hook 解析不同语言菜单,并通过 core helper 生成语言前缀。Core 与主题都不 import 或识别该插件;插件不存在时, 同一套 API 保持单语言行为。
内容与 Category/Tag 翻译、规范 URL 解析、菜单翻译、SEO alternate 和智能 跳转等能力见多语言插件。
