Caching and i18n
GoPress treats cache and internationalization as core services because both affect routes, templates, admin labels, settings, SEO, and frontend rendering.
Multi-level Cache
| Layer | Medium | Role |
|---|---|---|
| L1 | In-process LRU | Hot data with no network round trip. |
| L2 | Optional Redis | Shared cache for multi-process or multi-node deployments. |
| Full page | Reuses L1 and L2 | Returns complete HTML before theme rendering on a hit. |
| Database | PostgreSQL | Source of truth for content, options, menus, media, and extension data. |
Cache keys include the language dimension so translated pages never share HTML entries. Content writes, menu updates, settings changes, theme switches, and plugin activation changes invalidate the corresponding tags or cache paths. The admin cache page also provides manual all-cache, page-cache, and fragment-cache operations.
Redis is optional. When it is missing or unavailable, GoPress continues with the in-process cache and database rather than making the site unavailable.
Redis keys are namespaced per site as gopress:<namespace>:, and clearing all
caches or invalidating by prefix only touches the current site's keys. The
namespace comes from [redis].namespace or, when unset, is derived from the
database connection and table prefix, so several sites can safely share one
Redis database. See Configuration.
Core i18n Architecture
Core owns localization primitives; an optional multilingual plugin contributes database overrides and management UI through public contracts:
core/i18n.Manager
-> layered messages published as immutable bundle snapshots
-> LoadThemeLocales / LoadLocalesFS / AddMessages
-> T(ctx, key)
-> TranslateOption / TranslateSettings
core/option registry
-> RegisterTranslatable(key, section, label)
-> IsTranslatable / AllTranslatableKeys
resolution priority
1. override layer: database StringTranslation via AddMessages
domain="theme" for UI; domain="option" for settings
2. extension layer: namespaced plugin locale catalogs (LoadLocalesFS)
3. theme layer: the active theme's locales/ files
4. original message ID
A go-i18n bundle must not change while it is being read, so the manager never
mutates the bundle in use. After any layer changes it builds a new bundle from
all layers and publishes it atomically. Localizers keep the snapshot they were
created with, so in-flight requests finish consistently while new requests use
the new snapshot. Theme activation replaces the whole theme layer: after
switching to a theme that lacks a key (or has no locales/ directory), the
previous theme's text no longer leaks through. The database override layer
survives theme switches. Bundle() returns a read-only snapshot; extensions
change translations through AddMessages or LoadLocalesFS.
This fallback chain keeps a page usable when a translation is incomplete. Core remains a complete single-language CMS when no plugin supplies database translations.
UI Strings
Themes place JSON messages under locales/ and use {{T .Ctx "welcome"}} in
templates. A multilingual extension may add or override messages in the
database without editing theme files.
Translatable Settings
Themes register copy-oriented option keys such as hero headings or About text. Core translates only registered keys when rendering the settings map:
func registerTranslatableOptions() {
option.RegisterTranslatable("home_hero_title", "hero", "Hero title")
option.RegisterTranslatable("home_about_title", "about", "About title")
}
func (p *PageData) TranslateSettings(c *gin.Context, manager *i18n.Manager) {
p.Settings = manager.TranslateSettings(
c, p.Settings, option.IsTranslatable, option.AllTranslatableKeys())
}
Template Helpers
{{T .Ctx "welcome"}}
{{currentLang .Ctx}}
{{langPrefixURL .Ctx "/about"}}
{{archiveURL "product"}}
{{contentURL . "product"}}
{{taxonomyURL "category" .Slug}}
product is only an example theme-defined type. The URL helpers consult the
rewrite registry, so custom type names and changed rewrite_slug values do not
require hard-coded link changes.
Language Scope
A multilingual plugin can add language-aware content and term queries through the generic Content and Taxonomy Scope APIs, resolve per-language menus through menu hooks, and add URL prefixes through core helpers. Core and themes do not import or inspect that plugin; without it, the same APIs retain single-language behavior.
See the Multilingual Plugin for content and Category/Tag translation, canonical URL detection, menu assignment, SEO alternates, and language-switching behavior.
