真正能落地的是 github.com/nicksnyder/go-i18n/v2/i18n,它支持按请求新建 localizer、热加载 json、cldr 复数、rtl 适配及严格资源校验;bundle 必须全局单例初始化并一次性加载全部 active.*.json 文件,漏加载或文件名/结构错误均静默失败。

直接用 golang.org/x/text/message 做运行时语言切换,必出错——它不支持改语言,message.NewPrinter 一建好,language.Tag 就锁死了,后续任何 context 或 header 变更都无效。真正能落地的只有 github.com/nicksnyder/go-i18n/v2/i18n,它支持按请求新建 *i18n.Localizer、热加载 JSON、CLDR 复数、RTL 适配,且对资源格式有严格校验,错一点就静默返回空或原始 key。
go-i18n/v2 初始化必须全局单例 Bundle,不能每个请求重 load
Bundle 负责管理所有语言资源,是线程安全的,必须在应用启动时一次性初始化并加载全部 active.*.json 文件。漏加载一个,比如忘了 bundle.LoadMessageFile("locales/active.zh-Hans.json"),后续对该语言的 Localize 就会返回空字符串或原始 key,不 panic、不报错,极难排查。
- 用
os.DirFS("./locales")加载时,确保文件路径真实存在:如./locales/active.en-US.json - 不要用
i18n.MustLoadMessageFile,线上必须检查错误:_, err := bundle.LoadMessageFile(...) - 只加载
active.*前缀的文件,inactive.*或draft.*会被自动忽略 - Bundle 初始化后别再改动,也别试图 reload 已加载的文件(embed.FS 下需手动触发重解析)
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 到默认语言。
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 必须调
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 内重复解析
Localizer 必须每个请求新建,不能复用或缓存
*i18n.Localizer 是轻量、非线程安全、绑定具体语言的实例,复用会导致多语言混输出——比如 A 请求用中文,B 请求用英文,若共用一个 localizer,B 可能拿到 A 的翻译结果。
- 每个 HTTP 请求都应调
bundle.NewLocalizer(matched),不是bundle.Localize(...) - 别在 struct 字段里存
*i18n.Localizer,也不要在 init 里全局 new 一个 - 如果用 Gin/Echo,推荐通过
c.Set("localizer", localizer)注入,比塞 context.Value 更类型安全 - 不要在中间件里直接调
localizer.Localize,那是业务逻辑,应由 handler 控制
JSON 文件名和结构错一个字符就静默失败
go-i18n/v2 对资源文件极其敏感:文件名不合规、字段名写错、缺 description,都会导致 Localize 返回空或原始 key,且不报错。
- 文件名必须是
active.zh-Hans.json(不是zh.json、zh_Hans.json、zh-hans.json) - JSON 必须是对象结构:
{"login.title": {"description": "page title", "translation": "登录"}} - 不能简写成:
{"login.title": "登录"},也不能把translation写成msg或value - 字段名全小写,大写会静默忽略;只支持 JSON/TOML,不支持 YAML
- 启用
bundle.WithDebug(true)可在日志中看到加载失败的具体原因
最常被忽略的其实是 description 字段——它不是可选注释,而是解析校验必需项;还有就是 matcher 匹配后的 language.Tag 类型不能转成字符串再拼路径,必须原样传给 NewLocalizer。这两点一错,整个 i18n 就变成“看起来在跑,实际没生效”的黑盒。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










