国际化必须在请求进入业务逻辑前完成解析和绑定;accept-language 解析失败时应 fallback 到 language.english 而非 panic 或 language.undefined;每个请求需新建 message.printer 实例,不可复用全局实例;模板中需显式注册 printf 函数并传入当前请求的 printer;query 参数 lang 优先级高于 header。

c.Next() 之后的响应阶段无法再修改语言 tag —— 国际化必须在请求进入业务逻辑前完成解析和绑定,否则 printer 用的是旧语言上下文。
Accept-Language 解析失败时别 panic,fallback 到 language.English
HTTP Header 中的 Accept-Language 格式不规范很常见(比如空格、非法子标签、超出长度),language.Parse() 会返回 error。直接忽略或 panic 都会导致整个请求崩溃。
正确做法是 fallback 到默认语言:
tag, err := language.Parse(c.GetHeader("Accept-Language"))<br>if err != nil {<br> tag = language.English<br>}
注意:不要用
language.Undefined,它会导致 message.NewPrinter 初始化失败。
每个请求必须新建 message.Printer,不能复用全局实例
message.NewPrinter 确实线程安全,但它的行为依赖传入的 language.Tag。如果在 middleware 外部初始化一次并复用:var p *message.Printer = message.NewPrinter(language.Chinese)
那所有请求都会被强制渲染成中文,无论 header 是什么。
必须在每次中间件执行时按需创建:
- 从 header 或 query(如
c.Query("lang"))提取 tag - 调用
language.Make()构造合法 tag(比Parse更宽松) - 用该 tag 初始化
message.NewPrinter - 通过
c.Set("printer", p)注入上下文
模板里调用 p.Printf 前,得先注册为 template func
HTML 模板中写 {{ .printer.Printf "hello" }} 会报 can't evaluate field printer —— 因为模板引擎根本不知道 .printer 是啥。
解决方式是在初始化模板时显式注册:
t := template.Must(template.New("").Funcs(template.FuncMap{<br> "printf": func(s string, args ...interface{}) string {<br> if p, ok := c.Get("printer"); ok {<br> return p.(*message.Printer).Printf(s, args...)<br> }<br> return s<br> }<br>}))
注意:这里不能直接用
c(生命周期不对),实际要靠 closure 捕获当前请求的 printer 实例,或者改用 context-aware 的模板渲染封装。
query 参数 lang=zh-CN 和 header 冲突时,以哪个为准?
没有银弹规则,取决于你的产品策略。但常见且合理的选择是:
- 优先级:query 参数 > header > 默认语言
- 因为用户手动切换语言通常通过 URL 参数(如分享链接、书签),header 是浏览器自动带的,不可控
- 实现上别用
c.GetHeader("Accept-Language")硬覆盖,应先检查c.Query("lang"),非空则用language.Make(c.Query("lang")) - 注意
language.Make对格式容忍度更高(比如接受zh_CN、zh),而Parse要求严格 RFC 格式











