go-i18n/v2是gin最稳妥的国际化方案,因其支持运行时语言切换、cldr复数、json热加载及rtl适配;必须配合golang.org/x/text解析accept-language并匹配白名单,文件名须为active.zh-hans.json等合规格式,且需用embed或手动部署locales目录。

gin 本身不带国际化能力,必须靠第三方库补足。最稳妥、维护活跃、文档清晰的方案是用 go-i18n/v2 + golang.org/x/text,而不是过时的 gin-contrib/i18n(已归档,不支持 Go 1.20+,且 ini 格式难调试)。
为什么不能直接用 gin-contrib/i18n
这个库在 2022 年后就不再维护,go mod tidy 会报 invalid version: unknown revision;它依赖老版本 golang.org/x/text,和当前主流 Go 工具链冲突;更重要的是,它的 .ini 文件无法嵌套、不支持复数规则、没有类型安全校验——改个错别字就 runtime panic。
go-i18n/v2 的最小可行配置
只需 4 个文件就能跑起来,不写中间件、不碰路由分组,先让翻译动起来:
- 在项目根目录建
locales/目录,放en.json和zh-Hans.json -
en.json内容:{"hello": "Hello, {Name}!"} -
zh-Hans.json内容:{"hello": "你好,{Name}!"} - 在
main.go里初始化:bundle := i18n.NewBundle(language.English) bundle.RegisterUnmarshalFunc("json", json.Unmarshal) _, _ = bundle.LoadMessageFile("locales/en.json") _, _ = bundle.LoadMessageFile("locales/zh-Hans.json") localizer := i18n.NewLocalizer(bundle, "en") // 默认语言
如何在 handler 里安全取翻译
别硬编码语言标签,也别靠 c.Request.Header.Get("Accept-Language") 自己解析——这容易漏掉 zh-CN 和 zh-Hans 的映射。正确做法是用 golang.org/x/text/language 做匹配:
- 从请求头提取语言偏好:
accept := c.Request.Header.Get("Accept-Language") - 用
language.ParseAcceptLanguage(accept)得到[]language.Tag - 传给
localizer.Localize(&i18n.LocalizeConfig{MessageID: "hello", TemplateData: map[string]interface{}{"Name": "Alice"}}) - 如果返回空字符串,说明没匹配到翻译,应 fallback 到默认语言,而不是 panic
容易被忽略的路径和格式细节
实际部署时,locales/ 目录默认不会被打包进二进制文件——go build 只编译 .go 文件。这意味着你上线后看到的全是英文,因为 LoadMessageFile 找不到 JSON 文件。
- 要么把
locales/手动复制到运行目录(比如./locales/en.json) - 要么用
embed(Go 1.16+):在代码顶部加//go:embed locales/* var localeFS embed.FS
,再用bundle.MustLoadMessageFile(localeFS, "locales/en.json") -
zh-Hans.json不能写成zh-CN.json或zh.json——go-i18n/v2对 tag 匹配严格,zh-Hans≠zh-CN,除非你显式注册别名











