go-i18n需手动加载符合BCP 47规范的JSON语言文件,通过Accept-Language解析+URL参数校验确定语言,模板中须用带context的Localizer实例调用T函数,缺失翻译时需显式fallback而非依赖自动降级。
go-i18n 加载多语言 JSON 文件Go 官方标准库不提供开箱即用的国际化(i18n)支持,社区主流方案是使用 go-i18n(v2 版本,对应模块名 github.com/nicksnyder/go-i18n/v2/i18n)。它依赖结构化的 JSON 文件描述翻译内容,每个语言一个文件,如 active.en-US.json、active.zh-CN.json。
关键点在于:文件必须放在可被 i18n.NewBundle 读取的路径下,且需显式调用 bundle.LoadMessageFile;不能只靠文件名自动加载。
LoadMessageFile 返回 error,但常见错误(如文件不存在、JSON 格式错误)不会 panic,容易被忽略——务必检查返回值zh-CN 而非 zh_CN),否则 bundle.FindMessage 可能静默失败id 字段是查找键(不是 translation 文本本身),例如:{
"id": "welcome_message",
"translation": "Hello, {{.Name}}!"
}浏览器通过 Accept-Language 请求头告知服务端语言偏好,但该字段可能含多个带权重的标签(如 zh-CN,zh;q=0.9,en;q=0.8),不能直接取第一个。
推荐用 golang.org/x/net/webdav/acceptlang(轻量无依赖)或手动解析:提取所有标签 → 过滤出已支持的语言 → 按权重排序 → 取首个匹配项。不要硬编码 strings.Split(r.Header.Get("Accept-Language"), ",")[0]。
Accept-Language,应 fallback 到默认语言(如 en-US),而非空字符串?lang=ja-JP),但需校验该值是否在白名单中,防止目录遍历或无效语言导致 paniccontext.Context,避免在 handler 各处重复解析Go 的 html/template 不支持直接传入函数闭包,所以不能把 localizer.Localize 直接塞进 template.FuncMap 并期望它“记住”当前请求语言。必须为每次渲染构造带上下文的 localizer 实例。
典型做法:在 handler 内创建 *i18n.Localizer,绑定当前语言,再将其方法包装为模板函数:
func makeTemplateFuncs(loc *i18n.Localizer) template.FuncMap {
return template.FuncMap{
"T": func(id string, args ...interface{}) template.HTML {
msg, _ := loc.Localize(&i18n.LocalizeConfig{
MessageID: id,
TemplateData: map[string]interface{}{"Args": args},
})
return template.HTML(msg)
},
}
}
Localize 可能返回空字符串或原始 id(当翻译缺失时),前端显示前建议加 fallback 提示(如 [missing: {{.ID}}]){{.Name}})需与 TemplateData 键名严格一致,大小写敏感Localize 有时返回空字符串而不是默认语言文本这不是 bug,而是设计行为:Localize 默认只查找当前语言的翻译,不自动 fallback 到 bundle 默认语言(即使你调用了 bundle.SetDefaultLanguage)。要启用 fallback,必须显式设置 LocalizeConfig.DefaultMessage 或启用 bundle.RegisterUnmarshalFunc 配合多级 fallback 策略。
Localize 返回空时,再用默认语言 localizer 重试一次zh-Hans → zh-CN → en-US 多级 fallback)需自定义 i18n.LanguageTag 解析逻辑,不能依赖 bundle.FindMessage 自动降级go-i18n 自带的 i18n.MustT(panic on missing)辅助开发期发现遗漏语言切换不是加几个 JSON 就完事,真正难的是 fallback 策略的一致性、模板上下文隔离、以及错误路径的可观测性。别让一个没定义的 id 让整页变空白。