go-i18n/v2是唯一推荐的运行时多语言方案,因golang.org/x/text/message不支持动态切换语言;bundle须全局单例并一次性加载全部active.*.json文件,漏加载或文件名/结构错误均静默失败;accept-language必须经language.parseacceptlanguage解析后用matcher匹配白名单语言,再为每个请求新建localizer。

别用 golang.org/x/text/message 做页面文案的运行时语言切换——它初始化后 language.Tag 就锁死了,改 header、换 context 都无效,只会静默输出错语言。
go-i18n/v2 的 Bundle 必须全局单例且一次性加载全部 active.*.json
Bundle 是线程安全的资源管理器,不是每次请求都重建的对象。漏加载一个文件,比如忘了 bundle.LoadMessageFile("locales/active.zh-Hans.json"),后续对中文的 Localize 就会返回空字符串或原始 key,不 panic、不报错,极难定位。
- 启动时用
os.DirFS("./locales")加载前,确保目录下真实存在./locales/active.zh-Hans.json和./locales/active.en-US.json等全部目标文件 - 只加载
active.*.json前缀的文件,inactive.zh-Hans.json或draft.en-US.json会被自动忽略 - 线上必须检查错误:
_, err := bundle.LoadMessageFile(path),别用i18n.MustLoadMessageFile,它会在失败时 panic - Bundle 初始化完成后禁止再调
LoadMessageFile或修改内部状态——热重载需额外逻辑(如监听 fs.Stat 变更)
Accept-Language 解析必须走 language.ParseAcceptLanguage + Matcher
浏览器发来的 Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8 不是普通字符串。手动 strings.Split 会丢权重、误判变体(比如把 zh-Hans 当作和 zh-CN 不兼容),最终静默 fallback 到默认语言。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 先调
language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))得到有序的[]language.Tag - 预定义白名单:
supported := []language.Tag{language.Chinese, language.English} - 只初始化一次 matcher:
matcher := language.NewMatcher(supported) - 匹配得到归一化 tag:
matched, _ := matcher.Match(acceptTags),这个matched才能传给bundle.NewLocalizer - 建议把
matched存入req.Context(),避免 handler 内重复解析(内部有 map 查找开销)
Localizer 必须每个请求新建,不能复用或缓存
*i18n.Localizer 是轻量、非线程安全、绑定具体语言的实例。复用会导致多语言混输出——比如 A 请求用中文,B 请求用英文,共用一个 localizer 就可能 A 渲染出英文文案。
- 每个 HTTP 请求都应调
bundle.NewLocalizer(matched),不要试图缓存或全局复用 - 它初始化成本极低,只要
Bundle已预加载好,无需担心性能 - 模板中使用时,确保传入的是该请求专属的
*i18n.Localizer实例,不是字符串函数或全局变量 - 别在 struct 字段里存
*i18n.Localizer,也不要在中间件里提前调Localize——那是业务逻辑,应在 handler 或模板层触发
JSON 文件名和结构错一个字符就静默失败
go-i18n/v2 对资源格式极其敏感:文件名大小写、路径层级、JSON 字段名拼写,错一点就返回空字符串或原始 key,不报错。
- 文件名必须严格为
active.zh-Hans.json(不是zh.json、zh_Hans.json、active-zh-Hans.json) - JSON 内容必须含
description和translation字段:{"login.title": {"description": "page title", "translation": "登录"}} - 不能简写成
{"login.title": "登录"},也不能把字段名写成msg或value - 用
os.DirFS("./locales")时,路径必须完全匹配——./locales/active.zh-Hans.json缺少./locales/前缀就会加载失败
最常被忽略的点是:所有校验都是静默的。没有 error、没有 warning,只有空字符串或原始 key 出现在页面上。排查时得逐项核对文件名、JSON 结构、加载路径、matcher 白名单、Localizer 创建时机——少一个环节,就卡在“看起来没报错,但就是不翻译”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










