Go国际化核心是语言标签解析、资源加载和localizer使用三者协同;需用language.ParseAcceptLanguage解析Accept-Language,匹配白名单语言标签,按BCP 47规范命名JSON文件,全局复用Bundle并按请求创建Localizer。

Go 做国际化,核心不是选库,而是语言标签怎么解析、资源怎么加载、localizer 怎么用——三者错一个,localize("hello") 就返回空字符串或原 key。
Accept-Language 解析不能靠 strings.Split
浏览器发来的 Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 不是逗号分隔的简单列表,q 值决定优先级,还可能含非法标签(如 xx-YY)或空格。硬切第一个会掉进坑里。
- 必须用
language.ParseAcceptLanguage(来自golang.org/x/text/language),它自动排序、过滤无效项、标准化标签 - 解析结果要跟白名单比对:
supportedLangs := []language.Tag{language.Chinese, language.English, language.Japanese},别用字符串切片 - 匹配要用
matcher.Match,不是==:比如zh-Hans和zh-CN语义不同,但 matcher 能归一化识别 - 解析结果建议缓存到
req.Context(),避免每次 handler 都重复调用——ParseAcceptLanguage内部有 map 查找开销
go-i18n/v2 加载 JSON 文件的路径和命名必须严格
bundle.ParseFS 找不到文件?返回空翻译却不报错?大概率是路径或文件名没踩中规范。
- 文件名必须是
active.zh-CN.json,不是zh.json、zh_CN.json或zh-CN.json;子标签大小写、连字符、顺序都得符合 BCP 47 - 路径必须匹配
fs.FS的根:若用os.DirFS("./locales"),那文件就得在./locales/active.zh-CN.json,不能少一层目录 - JSON 结构必须是外层对象,每个 key 是 message ID,value 是
{"description": "...", "translation": "..."};缺description字段也会静默失败 - 别混放
active.*.json和inactive.*.json——只有active前缀的才会被自动加载
每个请求该用哪个 *i18n.Localizer?别全局单例,也别每次都 new Bundle
*i18n.Localizer 不是 goroutine-safe 的,但也不是每次请求都要重建 *i18n.Bundle——后者初始化成本高,前者轻量可复用。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 全局只持有一个
*i18n.Bundle,启动时一次性LoadMessageFile所有支持语言的资源 - 每个请求根据解析出的
language.Tag,调用bundle.NewLocalizer(lang)创建临时 localizer;它只是做一次 map 查找,性能足够 - 不要把 localizer 塞进
context.WithValue然后全链路透传——类型不安全、易漏、难调试;推荐在中间件里生成,然后作为参数传给 handler 闭包,或注入到 struct 方法接收器 - 模板渲染时,不能把 localizer 当函数塞进
FuncMap就完事;得提前绑定语言上下文,例如传入map[string]interface{}{"T": localizer.MustLocalize},否则模板里调{{.T "key"}}会 panic
为什么 MustLocalize 返回空?常见静默失败点
不是代码没跑,是某些环节“成功地失败了”:没报错,但啥也没翻译出来。
-
MessageID在 JSON 里不存在 → 返回原 key(不是错误) - JSON 文件结构不对(比如少
description字段)→ 静默跳过整条,不加载 - 语言 tag 不匹配任何已加载文件(比如你只加载了
active.en-US.json,却传language.Make("en"))→ fallback 失败,除非显式传[]string{"en-US", "en"} -
LocalizeConfig没设MessageID或拼写错误 → 返回空字符串 - 用了
localizer.Localize但没检查 error → 它返回(string, error),而MustLocalizepanic,别混用
最麻烦的不是不会写,是错得无声无息:空字符串、原始 key、页面突然变英文——这些往往卡在标签解析逻辑、文件路径拼接、或 JSON schema 校验上。盯住 language.Tag 和 active.*.json 这两个点,80% 的问题就定位了一半。

















