Docs / 缓存与 i18n

缓存与 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 和智能 跳转等能力见多语言插件。