go-i18n/v2是当前最稳的运行时多语言方案,需全局单例初始化bundle并显式校验加载error,accept-language必须用language.parseacceptlanguage+matcher解析,localizer须每请求新建,文件名、json结构及字段必须严格符合规范,否则静默返回空或原始key。

直接用 go-i18n/v2 是当前最稳的集成路径,但必须严格按规范走——错一个文件名、少一个字段、漏一次错误检查,都会导致 Localize 返回空字符串或原始 key,且不报错。
go-i18n/v2 初始化必须全局单例且校验加载结果
Bundle 是资源容器,必须在程序启动时一次性加载全部 active.*.json 文件,并显式检查每个 LoadMessageFile 的返回 error。静默忽略 error 是线上最常见的“翻译消失”原因。
-
bundle := i18n.NewBundle(language.English)只需调一次,全局复用 - 用
bundle.ParseFS(os.DirFS("./locales"), "active.*.json")加载,路径必须指向含active.zh-CN.json的目录 - 每条
ParseFS或LoadMessageFile调用后都得if err != nil { log.Fatal(err) }——i18n.MustLoadMessageFile会直接os.Exit(1),不适合生产环境动态加载场景 - 文件缺失或 JSON 格式错误(比如缺
description字段)不会 panic,但后续Localize就只返回 key
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 到默认语言却毫无提示。
- 调
language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))得到有序[]language.Tag - 预定义支持列表:
supported := []language.Tag{language.Chinese, language.English} - 全局初始化一次
matcher := language.NewMatcher(supported),再用matched, _ := matcher.Match(acceptTags)获取归一化后的 tag(如zh-Hans) - 匹配结果存入
req.Context(),避免每次 handler 重复解析
Localizer 必须 per-request 构造,不能复用或缓存
*i18n.Localizer 是轻量、非线程安全、绑定单个语言的实例。复用它会导致并发请求语言错乱;全局单例更不行——它不随请求变化。
- 每个 HTTP handler 入口调
localizer := bundle.NewLocalizer(matchedTag) - 这个操作只是 map 查找,开销极小,可安全用于高并发
- 别把
localizer塞进 struct 字段或全局变量——语言必须绑定 request 生命周期 - 模板渲染时传入的是
*i18n.Localizer实例,不是函数;localizer.Localize(&i18n.LocalizeConfig{MessageID: "login.title"})才是正确调用
JSON 文件名和结构稍有偏差就静默失败
go-i18n/v2 对文件系统和 JSON schema 极其敏感。写成 zh.json、active_zh-CN.json 或缺 description 字段,都不会报错,但 Localize 总是返回空或 key。
- 文件名必须严格为
active.zh-CN.json(注意是连字符,不是下划线或点) - JSON 内容必须是扁平对象,每个 key 对应一个 message ID,value 必须含
description和translation字段:{"login.title": {"description": "page title", "translation": "登录"}} - 不能简写为
{"login.title": "登录"},否则解析成功但运行时无效果 - 用
embed.FS打包时,确保embed路径与ParseFS中路径一致,相对路径在go run和go build下行为不同
最容易被忽略的是:所有语言文件的 message ID 集合必须完全一致。缺一个 login.title 在 active.ja-JP.json 里,Localize 就不会 fallback 到英文,而是直接返回 "login.title" —— 这不是 bug,是设计使然,靠测试覆盖和 CI 检查才能提前发现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











