国际化模板缓存键必须包含 locale 参数,否则不同语言用户会互相覆盖导致缓存污染;需显式拼入 locale 及设备、AB 实验等维度,并优先从 Accept-Language 或路径前缀提取 locale;含内联 JS 的模板应禁用缓存或改为客户端加载;CDN 缓存需设 Vary: Accept-Language;TTL 应按语言更新频率差异化设置,并精准清除对应 locale 缓存。

国际化模板缓存键必须包含 locale 参数
模板渲染结果依赖语言环境时,缓存键若只用文件名(如 header.html),不同语言用户会互相覆盖——中文用户看到英文文案,或反之。这不是缓存没生效,而是缓存污染。
服务端模板引擎(Django/Jinja2/Go template)本身不自动感知 locale 变量,必须显式拼入缓存键:
-
cache.Get("header:zh-CN")和cache.Get("header:en-US")是两个独立缓存项 - 若页面还区分设备类型或 AB 实验组,需一并加入:
header:zh-CN:mobile:exp=checkout-v2 - 从请求中提取 locale 时,优先信任
Accept-Language头或路径前缀(如/zh-CN/home),而非 cookie 或 query string——后者易被篡改且难统一
含内联 JS 的国际化模板禁用缓存
比如模板里有 <script>window.LANG = "{{.Locale}}";</script>,SSR 渲染后 JS 已执行一次;若该 HTML 片段被缓存并复用,客户端再次执行同一段脚本,window.LANG 可能被覆盖或重复初始化,导致 i18n 库状态错乱。
这类模板应直接跳过缓存层,或改为纯静态结构 + 客户端按需加载语言包:
立即学习“前端免费学习笔记(深入)”;
- 移除模板内所有
<script>和<style>标签,只保留语义化 HTML - 把语言变量抽到 JSON 接口(如
/api/i18n?lang=zh-CN),由前端 fetch 后注入 - 若必须内联,至少确保 JS 不含副作用:用
const LANG = "{{.Locale}}"替代赋值语句
CDN 缓存 HTML 模板碎片时必须加 Vary: Accept-Language
当碎片走独立 HTTP 请求(如 HTMX 的 hx-get="/fragments/nav?lang=zh-CN"),CDN 默认按 URL 缓存,/fragments/nav?lang=zh-CN 和 /fragments/nav?lang=en-US 虽然参数不同,但部分 CDN(如 Cloudflare 免费版)会忽略 query string,导致缓存混用。
更可靠的做法是让服务端响应头带上 Vary: Accept-Language:
- HTMX 请求中检测到
HX-Request: true时,同时检查Accept-Language并设Vary头 - Nginx 可配置:
add_header Vary "Accept-Language";,但需确认未被 upstream 覆盖 - 避免只靠 query string 区分——CDN 可能 strip 参数,而
Vary是 HTTP 标准级协商机制,兼容性更好
服务端模板缓存失效策略要匹配语言更新频率
中文翻译可能每月更新一次,而法语翻译半年不动;若所有 locale 共享同一套缓存 TTL(如 1 小时),就会出现“中文已上线新文案,法语还在用旧缓存”的割裂感。
建议按语言维度差异化控制:
- 高频语言(zh-CN、en-US)设 TTL = 600 秒(10 分钟)
- 低频语言(sw-KE、my-MM)设 TTL = 86400 秒(24 小时)
- 发布新语言包时,主动触发对应缓存清除:
cache.Delete("footer:sw-KE"),而不是等自然过期 - 不要用全局
cache.FlushAll()——它会把所有语言版本一起清掉,引发瞬时回源压力
zh-Hans-CN vs zh-Hant-TW)、fallback 链(en-US → en),缓存键必须精确反映最终解析出的语言 ID,否则 fallback 逻辑会在缓存层外暴露不一致。



















