go 的 error 接口不支持 i18n,需自定义错误类型封装翻译键、参数及 localizer;推荐 go-i18n(适合动态多语言)或 x/text/message(轻量固定语言);http 中通过 context 传 localizer,避免 defer 翻译;panic 不应国际化,仅日志记录键名。

Go 的 error 接口本身不支持 i18n,必须自己包装
Go 标准库的 error 是个接口,只保证有 Error() 方法返回 string。它不带语言上下文、不存翻译键、也不管你用中文还是阿拉伯语——返回啥就是啥。所以直接拿 fmt.Errorf("user not found"),永远只能是英文。
想让错误提示多语言,得绕开原生 error 的“字符串即一切”假设,自己定义可翻译的错误类型:
- 定义一个结构体,内嵌原始错误(比如
err error),再加一个翻译键(比如key string)和参数(比如args []interface{}) - 实现
Error() string时,不直接返回硬编码文本,而是调用 i18n 翻译器(如localizer.Localize(&i18n.LocalizeConfig{...})) - 关键点:
Error()方法里不能做耗时操作(比如查数据库),也不能 panic;翻译器必须是线程安全且已初始化好的
用 go-i18n 或 golang.org/x/text/message 做运行时翻译?选哪个
go-i18n(v2)适合需要 JSON 翻译文件、动态加载语言包、支持复数规则的场景;golang.org/x/text/message 更轻量,但只做格式化(printf 风格),不管理错误键或语言切换逻辑。
实际选型看两点:
立即学习“go语言免费学习笔记(深入)”;
- 如果你已有
en.json/zh.json这类资源文件,且要支持用户切换语言,go-i18n是更省心的选择;它的Localizer能按Accept-Language自动匹配,也支持 fallback - 如果只是服务端固定语言(比如后台任务只输出日志),用
message.Printer+message.SetString就够了,启动时注册好对应语言的模板即可 - 注意:
go-i18n的Localize在找不到 key 时默认返回 key 字符串(比如"user_not_found"),不是 panic,这点要提前测清楚
HTTP handler 中怎么把请求语言注入到错误里
错误本身不该知道 HTTP 头,但 handler 是语言上下文最自然的入口。别在错误创建时硬塞 *http.Request,而应把语言信息提前提取出来,传给翻译层。
- 从
r.Header.Get("Accept-Language")解析出首选语言(用language.Parse+language.MatchStrings) - 构造一个带语言标识的
localizer实例(比如localizer := i18n.NewLocalizer(bundles, lang.String())),然后把它作为参数传给业务函数,或通过 context 传递(context.WithValue(ctx, localizerKey, localizer)) - 避免在
defer里调用localizer.Localize—— 如果 handler 已经写了 header,再改错误消息语言就晚了;建议统一在写响应体前集中处理错误翻译
panic 里的错误能国际化吗
不能,也不该尝试。
panic 是程序异常中断机制,不是用户提示通道。你看到的 panic: user not found 是开发期调试信息,生产环境应该捕获并转成 HTTP 4xx/5xx 响应,再走正常 i18n 流程。
- 不要在
recover后直接调用localizer.Localize—— 此时 request 上下文大概率已丢失,语言信息不可靠 - 真正该国际化的,是
http.Error或 JSON 响应体里的message字段,不是 panic 日志 - 日志系统(如 zap)记录 panic 时,建议只记原始 error 键(
"user_not_found")和 args,翻译留给前端或告警平台做
最麻烦的其实是错误键的设计:键名要不要带模块前缀(auth.user_not_found vs user_not_found),参数要不要限制为基本类型,翻译缺失时是 fallback 到英文还是直接暴露键名——这些没标准答案,但一旦定下来,就得全项目遵守,否则 localization 会变成维护黑洞。










