go-i18n/v2 的 bundle 初始化必须全局仅一次:在 main() 中创建单例 bundle 并用 ParseFS 一次性加载 locales 下符合命名规范(如 active.zh-Hans.json)的 JSON 文件,结构需含 description 和 translation 字段;Accept-Language 必须用 language.ParseAcceptLanguage 解析并经 NewMatcher 匹配白名单语言,每请求动态调 bundle.NewLocalizer(tag) 获取线程安全的 Localizer 实例,禁止复用或缓存。

go-i18n/v2 的 bundle 初始化必须在全局只做一次
加载翻译文件不是每次请求都重新 parse,而是启动时一次性注入到 bundle 实例里。如果在中间件或 handler 里反复调 bundle.ParseFS,会导致资源重复加载、内存泄漏,甚至覆盖已注册的翻译项。
- 正确做法:在
main()或初始化函数中创建单例bundle := i18n.NewBundle(language.English),再用bundle.ParseFS(fs, "locales/active.*.json")加载全部文件 - 路径必须匹配:
fs是os.DirFS("./locales"),且文件名严格为active.zh-Hans.json、active.en-US.json—— 缺前缀、大小写错、连字符写成下划线,都会被静默跳过 - JSON 结构必须合规:外层是对象,每个 key 是 message ID,值必须是
{"description": "", "translation": ""};缺description字段,该条目直接丢弃
Accept-Language 解析必须用 language.ParseAcceptLanguage
浏览器发来的 Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8 不是普通字符串,手动切分或正则提取会丢失权重、误判语言兼容性(比如把 zh-Hans 当作和 zh-CN 不匹配),最终 fallback 到默认语言却毫无提示。
- 必须调
language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))得到[]language.Tag - 维护一个白名单
supported := []language.Tag{language.Chinese, language.English, language.Japanese} - 只初始化一次
matcher := language.NewMatcher(supported),然后用matcher.Match(acceptLang...)获取最匹配的language.Tag - 这个
Tag后续直接传给bundle.NewLocalizer,别转成字符串再解析
Localizer 必须按请求动态构造,不能复用
bundle.NewLocalizer(langTag) 开销极小,但 *i18n.Localizer 实例不是线程安全的,也不能跨请求复用。如果在中间件里存成全局变量或 context.Value,多个并发请求会互相覆盖翻译结果。
- 每个请求拿到匹配后的
langTag后,立刻调localizer := bundle.NewLocalizer(langTag) - 翻译时用
localizer.Localize(&i18n.LocalizeConfig{MessageID: "auth.login.title"}),注意是方法调用,不是函数 - 不要试图缓存
Localizer实例——它内部不持有大量状态,重建成本可忽略 - 若返回空字符串或原始 key(如
"auth.login.title"),优先检查langTag是否与文件名完全一致(包括大小写、连字符)、MessageID是否拼写错误、对应 JSON 中是否存在该 key
Gin 中间件里绑定 localizer 到 context 的正确姿势
很多人把 Localizer 存进 c.Set("localizer", localizer) 就以为完事了,但后续 handler 里忘记取或类型断言出错,导致翻译失效且无报错。
立即学习“go语言免费学习笔记(深入)”;
- 推荐封装一个
GetLocalizer(c *gin.Context) *i18n.Localizer函数,统一做类型断言 + panic 防御 - 中间件中:
c.Set("localizer", bundle.NewLocalizer(matchedTag)) - handler 中:
localizer := GetLocalizer(c),然后localizer.Localize(...) - 避免在模板渲染时直接传
localizer,Gin 的HTML渲染不支持方法调用;应提前在 handler 中完成翻译,把结果传入c.HTML(200, "index.html", gin.H{"title": translatedTitle})
language.Tag 的精确匹配——active.zh-CN.json 和 active.zh-Hans.json 是两个完全不同的语言变体,language.Make("zh-CN") 拿不到后者里的翻译,哪怕内容一模一样。


















