message.printer 必须 per-request 创建,不可复用或设为包级变量;viper 多语言配置需为每种语言单独实例化;go-i18n 文件扩展名须严格匹配;yaegi 动态脚本风险高,推荐预编译资源。

golang.org/x/text/message 必须 per-request 创建 Printer
Go 没有全局语言环境,message.Printer 不是单例,也不能复用跨请求。常见错误是把 message.NewPrinter 结果存成包级变量,结果并发请求互相覆盖、语言错乱。
正确做法是在每个 HTTP handler 开始时解析 Accept-Language,构造新 Printer:
func handler(w http.ResponseWriter, r *http.Request) {
tags, _, _ := language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))
tag := language.English
if len(tags) > 0 {
tag = tags[0]
}
p := message.NewPrinter(tag) // 每次新建
p.Printf("Welcome back, %s!\n", username)
}
- 不调
message.SetDefault()——它只影响未传 tag 的 fallback 行为,且不可逆 - 未注册翻译时,
p.Printf静默回退到英文原文,不会 panic 或报错 - 若需复用
Printer(如 CLI 工具),应从命令行 flag 解析后显式传入,而非依赖隐式状态
viper 加载多语言配置文件要避免 merge 冲突
用 viper 管理多语言资源(如 lang/en.yaml、lang/zh.yaml)时,不能直接 viper.ReadInConfig() 多次——它会 merge 后续内容进当前配置,导致键值污染。
推荐做法是:为每种语言单独实例化 viper.Viper,并隔离加载路径:
func loadLangConfig(lang string) *viper.Viper {
v := viper.New()
v.SetConfigName(lang)
v.AddConfigPath("./lang")
v.SetConfigType("yaml")
v.ReadInConfig() // 此处失败会 panic,需加 error check
return v
}
en := loadLangConfig("en")
zh := loadLangConfig("zh")
- 不要用
viper.SetConfigName("lang-" + lang)然后共用一个 viper 实例——命名规则不统一易出错 - YAML 文件中避免顶层 key 冲突(如都写
greeting: Hello),否则 merge 后只剩最后一个 - 若语言包较大,可预编译为 Go map 变量(如
langs["zh"] = map[string]string{...}),跳过 runtime 解析开销
go-i18n 的 LoadMessageFile 要注意路径和扩展名匹配
go-i18n/v2 的 bundle.LoadMessageFile() 对文件路径和扩展名敏感:它会根据文件名后缀自动选择 unmarshal 函数,但不会自动补全或忽略大小写。
典型失败现象:LoadMessageFile("locales/zh.json") 返回 nil error,但后续 localizer.MustLocalize 仍输出英文——实际是文件没被识别为 JSON。
- 确保文件扩展名小写且与
RegisterUnmarshalFunc注册的完全一致(如"json"≠"JSON") - 路径必须可访问;在 Docker 中若挂载到
/app/locales,代码里就得写"./locales/zh.json"或绝对路径 - 不建议用通配符加载(如
LoadMessageFiles("locales/*.json")),出错时难以定位具体哪个文件失败
Yaegi 动态执行语言切换脚本的风险点
用 Yaegi 在运行时加载 lang_switch.yaegi 执行切换逻辑,看似灵活,但有几个硬伤:
- Yaegi 解释器本身不带 sandbox 默认启用全部标准库,
os.Exit、net/http等危险调用无法拦截 - 脚本中若用
import "fmt",会触发 Yaegi 自行 resolve,但不同版本 Go 标准库兼容性不保证(如 Go 1.25 的fmt可能被 Go 1.23 的 Yaegi 错误解析) - 错误堆栈指向
lang_switch.yaegi:12,但调试时无法设断点、无 IDE 支持,线上排查成本高
除非你明确需要「用户上传脚本修改 UI 文案」这类运营能力,否则优先用 golang.org/x/text/message + 预编译资源,更可控。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











