应在中间件中为每个请求解析语言标签并创建独立localizer实例,调用bundle.newlocalizer(tag)生成轻量级localizer,再通过c.set("t", tfunc)注入上下文,禁止全局复用t函数。

gin.Context里怎么安全注入每请求独立的翻译函数
不能复用全局 T 函数,否则并发请求会互相覆盖语言偏好。Gin 的 c.Request.Context() 是天然隔离的载体,但必须在中间件中为每个请求生成新的 localizer 实例。
- 全局只持有一个
*i18n.Bundle,启动时用bundle.ParseFS(os.DirFS("./locales"), "active.*.json")预加载所有active.zh-CN.json、active.en.json等资源 - 中间件里调用
language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))解析,再用MatchStrings(supported, tags...)匹配白名单语言(如[]string{"zh-CN", "en"}) - 匹配后调用
bundle.NewLocalizer(tag)创建轻量级*i18n.Localizer,它不是 goroutine-unsafe 的,可放心每请求新建 - 把 localizer 方法绑定成函数塞进 Gin context:
c.Set("T", func(msgID string, args ...interface{}) string { return localizer.Localize(&i18n.LocalizeConfig{MessageID: msgID, TemplateData: args}) })
模板渲染时如何让 html/template 或 gin.HTML 自动用对语言
gin.HTML 默认不感知语言上下文,必须手动传入翻译函数或提前渲染好文本。直接在 c.HTML() 里传 gin.H{"T": c.MustGet("T").(func(string, ...interface{}) string)} 是最直白的做法。
- HTML 模板里写
{{ .T "login_title" }},而不是硬编码登录或依赖 JS 切换 - 别把
T函数注册成 template.FuncMap 全局函数——它依赖请求级localizer,全局注册会导致所有请求共享同一个语言实例 - 如果用了
html/template自定义解析(比如预编译模板),需确保每次执行t.Execute(w, data)前,data已含当前请求的T函数 - 注意:
localizer.Localize()返回空字符串或原始 key,大概率是 JSON 文件名错(缺active.前缀)、字段结构不对(缺description)、或路径没被ParseFS扫到
validator 错误消息怎么和模板语言保持一致
validator 的错误默认是英文字符串,它和 Gin 模板用的 i18n 体系完全无关。必须显式桥接:用 universal-translator 注册对应语言的翻译器,再调用 ValidationErrors.Translate()。
- 初始化时创建
ut.UniversalTranslator,传入zh.New()和en.New()等 locale 实例 - 从 Gin context 取出当前语言 tag(比如
c.GetString("lang")),再用uni.GetTranslator(lang)获取对应 translator - 调用
zh_translations.RegisterDefaultTranslations(validate, trans)注册模板,注意第一个参数是*validator.Validate实例 - 校验失败后,
errs.Translate(trans)才会输出中文;漏掉这步,errs.Error()永远是英文
为什么切换语言后部分文案还是旧的
常见原因不是代码没写对,而是资源加载或缓存没清理干净。go-i18n/v2 不支持热重载,文件改了也不会自动生效;而浏览器或反向代理可能缓存了 HTML 或 JSON 资源。
- 开发期加
bundle.WithDebug(true),能让Localize()在找不到翻译时返回带提示的字符串(如MISSING_TRANSLATION: welcome),方便定位漏配项 - 确保
Accept-Languageheader 被正确传递——Nginx 默认不透传,需加proxy_set_header Accept-Language $http_accept_language; - 前端切换语言时,别只改 URL 参数(如
?lang=ja),要同步设置X-Request-Languageheader,否则中间件读不到 - JSON 资源文件必须放在
./locales/active.ja.json,写成ja.json或locales/ja.json都会静默失败











