所有业务错误必须用自定义结构体封装,error()仅返回code字符串,翻译严格延迟至http handler末尾或cli输出前,通过messageid查表实现多语言,确保错误链完整、日志可分析、errors.is有效。

错误不能在创建时就翻译,必须延迟到响应前
Go 的 error 接口返回的是固定字符串,一旦用 errors.New("用户不存在") 或 fmt.Errorf("数据库连接失败: %w", err) 写死文本,后续就无法按语言切换——不是技术做不到,而是会破坏错误链、干扰日志分析、让 errors.Is 失效。
- 所有业务错误必须用自定义结构体封装,比如
AppError{Code: "user_not_found", Args: []interface{}{"alice"}} -
Error()方法只返回Code字符串,不拼接任何语言内容 - 翻译动作严格限定在 HTTP handler 末尾或 CLI 输出前,此时已知
Accept-Language和用户偏好 - 非本地化错误(如
os.PathError)保持原样透传,可加前缀标注来源,例如"system_error: permission denied"
用 MessageID 当钥匙,别把语言塞进 error 结构里
MessageID 是纯标识符,像 "user_not_found" 或 "auth.token_expired",它不带空格、不带标点、不带语言信息,只用于查表。一旦在结构体里存了中文或英文文案,就等于把 locale 耦合进了错误创建逻辑,同一错误在不同请求中可能被反复翻译成不同语言,导致日志混乱、监控失真。
- 资源文件用 JSON 格式,每个
MessageID对应一个对象,含description和带占位符的translation - 示例:
{"user_not_found": {"description": "User with given name does not exist", "translation": "用户 {{.Name}} 不存在"}} - 调用翻译时传
map[string]interface{}{"Name": "alice"},由 i18n 库处理语序差异 - 避免在 JSON 里写条件逻辑(如“如果数量>1就用复数”),复杂分支交给 Go 代码控制,拆成多个独立
MessageID
go-i18n 加载和查找有隐性规则,错一个就静默失败
go-i18n/v2 不会自动扫描目录加载文件,也不会 fallback 到默认语言——它靠你显式调用 bundle.LoadMessageFile,且文件名必须符合 BCP 47 规范(zh-CN.json 可以,zh_CN.json 就找不到)。
- 加载后务必检查
LoadMessageFile返回的error,常见错误是路径不对或 JSON 格式非法,但不会 panic -
LocalizeConfig中的Language必须是已加载的语言标签,否则bundle.FindMessage静默返回空 - 解析
Accept-Language不能直接strings.Split(..., ",")[0],要用golang.org/x/net/webdav/acceptlang按权重排序并过滤白名单 - URL 参数
?lang=ja-JP可覆盖 header,但必须校验是否在支持列表内,防止路径遍历或无效 tag 导致 panic
HTTP 响应体要同时返回 code、error_id 和 localized message
前端需要 code 做逻辑判断(比如跳登录页),运维需要 error_id 查日志上下文,用户才需要 localized message。三者缺一不可,只返回翻译后的字符串等于扔掉调试线索。
立即学习“go语言免费学习笔记(深入)”;
- 响应结构建议统一为:
{"code": "auth.user_required", "error_id": "a1b2c3d4", "message": "请先登录"} -
error_id是随机生成的唯一 trace ID,和日志中的request_id关联,方便排查 - 不要在
fmt.Errorf里调localizer.Translate,这会让错误链里混入多语言文本,日志搜索失效 - CLI 场景同理:命令行输出展示翻译后消息,但
os.Exit(1)前仍要记录原始Code和Args到 stderr
最常被忽略的一点是:错误码和 JSON 翻译 key 的一致性没法靠编译器保证,得靠 CI 脚本做校验——比如扫描所有 AppError{Code: "..."} 实例,比对是否都在 en.json 和 zh-CN.json 里存在对应项。漏一条,线上就有一处错误永远显示 “unknown_error”。










