最稳妥方案是用 go-i18n/v2 配合 gin 中间件,避免使用多年未维护的 gin-contrib/i18n;需严格匹配 json 路径与格式,确保字段小驼峰、无注释和尾逗号;语言检测顺序为 accept-language → cookie → url query → fallback,首次无语言标识则直接回退且不自动更新;validator 错误翻译须独立初始化 translator 并全局复用实例。

直接用 go-i18n/v2 配合 Gin 中间件是最稳妥的方案,gin-contrib/i18n 已多年未维护,不建议新项目使用。
加载翻译文件时路径和格式必须严格匹配
Go 语言对 JSON 文件的解析很严格:字段名必须小驼峰(如 hello_world),值必须是字符串,不能有 trailing comma,注释也不允许。常见错误是误用 INI 或 YAML 格式,或把 zh-Hans.json 命名为 zh-CN.json 却在代码里写 zh-Hans —— 这会导致 GetMessage 返回空字符串且无报错。
- 推荐目录结构:
locales/en-US.json、locales/zh-Hans.json、locales/ja-JP.json - 每个文件顶层必须是 JSON object,不能是 array 或纯字符串
- 加载时用绝对路径或基于
os.Executable()构造路径,避免因工作目录不同导致OpenFile: no such file
i18n.Handler() 中语言检测顺序决定实际行为
默认中间件按 Accept-Language 请求头 → Cookie → URL query(如 ?lang=zh-Hans)→ fallback locale 的顺序取语言标识。但很多人忽略了:如果用户第一次访问没带 Accept-Language(比如某些爬虫或旧客户端),且你没设 Cookie 或 query,就会直接 fallback 到英文,且后续不会自动更新 —— 这不是 bug,是设计使然。
- 显式覆盖检测逻辑:在
i18n.Handler()前插入自定义中间件,从 JWT token、数据库用户偏好或 session 读取语言 - Cookie 名必须与
i18n.CookieName一致,默认是lang;若改过,务必同步调用i18n.SetCookieName("mylang") - URL query 参数名默认为
lang,可通过i18n.QueryName = "l"修改,但前端跳转链接必须同步更新
validator 错误信息翻译需独立初始化 translator
Gin 的 binding.Validator 和 i18n 包用的是两套翻译器,即使都用了 zh-Hans,也不会自动共享。不单独配置,表单校验永远显示英文提示。
- 必须调用
zh_translations.RegisterDefaultTranslations(v, trans),其中v是 validator 实例,trans是ut.Translator实例 -
trans必须和 i18n 模块用同一 locale(如都用zh-Hans),否则uni.GetTranslator("zh-Hans")返回的 translator 无法被 validator 识别 - 结构体 tag 中的错误 key(如
required)必须和翻译包里定义的 key 完全一致,大小写、连字符都不能错
最容易被忽略的是 validator 和 i18n 的 translator 实例生命周期管理 —— 它们不能是局部变量,必须全局持有并确保在所有请求中复用同一个实例,否则并发下会 panic 或翻译失效。











