go-i18n/v2是唯一能真正落地的运行时多语言方案,因golang.org/x/text/message不支持运行时语言切换;localizer.localize返回空或原始key是配置未达标所致,常见原因包括文件名非法(须active.zh-hans.json)、路径错误、json缺description/translation字段、未调loadmessagefile、语言标签未注册;accept-language必须用language.parseacceptlanguage解析并经matcher匹配白名单;bundle须全局单例,localizer须每请求新建。

go-i18n/v2 是目前唯一能真正落地的运行时多语言方案,别用 golang.org/x/text/message 做动态语言切换——它不支持运行时改语言,硬上只会返回错语言且查不出原因。
为什么 localizer.Localize 返回空字符串或原始 key
这不是 bug,是配置未满足硬性规范导致的静默失败。常见原因包括:
- 文件名不合法:必须是
active.zh-Hans.json,zh.json、zh_Hans.json、active-zh-hans.json全部被忽略 - 路径错误:用
os.DirFS("./locales"),但文件实际在./i18n/active.en-US.json,加载直接失败 - JSON 结构错:必须是
{"login.title": {"description": "page title", "translation": "登录"}};缺description字段、把translation写成msg或value,都会返回空 - 没调
bundle.LoadMessageFile:v2不自动扫描目录,不显式加载就等于没注册语言资源 - 语言标签未注册:传给
bundle.NewLocalizer的language.Tag没对应已加载的active.*.json文件
Accept-Language 解析必须用 language.ParseAcceptLanguage
浏览器发来的 Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7 不是普通逗号分隔字符串,而是带权重、可嵌套、需标准化的语言标签序列。手动 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),别每次请求都 new - 匹配:
matched, _ := matcher.Match(acceptTags),返回归一化后的language.Tag(如zh-Hans)
bundle 和 localizer 必须严格分工
*i18n.Bundle 是线程安全的全局单例,负责管理所有语言资源;*i18n.Localizer 是轻量、非线程安全、按需绑定语言的实例,必须每个请求新建。
- 启动时只建一个
bundle := i18n.NewBundle(language.English),然后用bundle.ParseFS(fs, "locales/active.*.json")加载全部资源 - 每个 HTTP 请求中,根据解析出的
langTag调localizer := bundle.NewLocalizer(langTag)—— 这个操作很轻,不用缓存 - 调
localizer.Localize(&i18n.LocalizeConfig{MessageID: "auth.login.title"}),注意是方法调用,不是函数 - 别把
localizer塞进context.WithValue全链路透传——类型不安全、易漏、难调试;推荐中间件生成后作为参数传入 handler
JSON 文件命名和结构最容易踩坑
go-i18n/v2 对文件名和 JSON 结构极其敏感,错一个字符就静默失败(返回空字符串或原始 key,不 panic,极难排查)。
- 文件名必须是
active.zh-Hans.json(不是zh.json、zh_Hans.json、zh-hans.json) - 路径要对:如果用
os.DirFS("./locales"),那文件必须在./locales/active.zh-Hans.json - JSON 内容必须含
description和translation字段:{"login.title": {"description": "page title", "translation": "登录"}} - 不能简写成:
{"login.title": "登录"}—— 这会导致解析成功但后续Localize返回空
Localize 就会安静地交出原始 key,连 warning 都不报。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











