go-i18n/v2是唯一推荐的运行时多语言方案,因其支持按请求动态切换语言、CLDR复数、热加载及RTL适配;而golang.org/x/text/message不支持运行时切换,初始化后language.Tag即固化。

直接用 go-i18n/v2,别碰 golang.org/x/text/message 做页面文案或错误提示——它不支持按请求切换语言,printer.Printf("hello") 一旦创建就永远输出初始语言。
Accept-Language 解析必须用 language.ParseAcceptLanguage
浏览器发来的 Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8 不是普通字符串,含权重、非法标签、空格和嵌套变体。手动 strings.Split 或正则会漏匹配、误 fallback。
- 必须调
language.ParseAcceptLanguage(r.Header.Get("Accept-Language")),它自动过滤无效项、排序、标准化(如把zh-Hans归一为zh) - 结果是
[]language.Tag类型,不是字符串数组;后续匹配要用language.Matcher,不能用==比较 - 建议缓存解析结果:用
sync.Map存string → language.Tag映射,避免每次请求重复 map 查找开销 - 若 header 为空或含
fr-XX这类非法 tag,ParseAcceptLanguage会静默跳过,不会 panic
go-i18n/v2 的资源加载对文件名和结构极其敏感
bundle.ParseFS 加载失败时不会报错,只会返回空字符串或原始 key——这是最常踩的坑。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 文件名必须严格为
active.zh-CN.json,写成zh.json、active_zh-CN.json或cn.json都会静默忽略 - 路径必须匹配
os.DirFS("./locales"):文件得在./locales/active.zh-CN.json,少一层目录就找不到 - JSON 结构必须含
description和translation字段:{"welcome": {"description": "homepage greeting", "translation": "欢迎"}}✅;{"welcome": "欢迎"}❌ - 只加载
active.*.json,inactive.*.json被设计为忽略,不是 bug
每个 HTTP 请求必须动态构造 *i18n.Localizer
*i18n.Localizer 不是全局单例,也不能复用,但初始化极轻量——只要 *i18n.Bundle 已预加载好。
立即学习“go语言免费学习笔记(深入)”;
- 全局只建一个
bundle := i18n.NewBundle(language.English),启动时一次性调bundle.ParseFS加载全部active.*.json - 每个请求中,根据解析出的
language.Tag调bundle.NewLocalizer(langTag),不要缓存这个 localizer - 调用时传
&i18n.LocalizeConfig{MessageID: "auth.login.title"},注意是方法调用:localizer.Localize(...),不是函数localize(...) - 别把
localizer塞进context.WithValue全链路透传——类型不安全;推荐在中间件里生成,然后作为参数传给 handler 闭包
最容易被忽略的是 JSON 文件的 description 字段和文件名中的连字符大小写——少一个字母或错一个位置,Localize 就返回空,日志里还看不到任何提示。

















