应直接使用 go-i18n/v2,因其支持按请求动态切换语言、cldr复数、热加载及rtl适配;而 golang.org/x/text/message 不支持运行时切换,初始化后 language.tag 即固化,导致所有请求输出初始语言。

直接用 go-i18n/v2,别碰 golang.org/x/text/message 做 Echo 的页面文案或错误提示——它不支持按请求切换语言,message.NewPrinter 一创建,language.Tag 就固化了,后续所有请求都输出初始语言,且查不出原因。
为什么 Echo 中 localizer.Localize 总是返回空或原始 key
这不是库的 bug,是资源加载链路中任一环节没踩准规范导致的静默失败。最常见原因包括:
- 文件名不合法:必须是
active.zh-Hans.json,zh.json、zh_Hans.json、active-zh-hans.json、zh-CN.json全部被忽略 - JSON 结构缺字段:必须含
description和translation,写成{"login.title": "登录"}会解析成功但Localize()返回空 - 路径不匹配:若用
os.DirFS("./locales"),文件就得在./locales/active.zh-Hans.json,多一层或少一层目录都不行 - 没调
bundle.ParseFS:v2 不自动扫描,不显式加载就等于没注册任何翻译 - 传给
bundle.NewLocalizer的language.Tag没对应已加载的active.*.json文件(比如加载了active.zh-Hans.json,却传language.MustParse("zh-CN"))
如何在 Echo 中安全解析 Accept-Language 并匹配白名单
浏览器发来的 Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8 是带权重、可嵌套、需标准化的语言标签序列,不能 strings.Split 或正则硬切。
Echo框架 5.1.0 版本源码包下载,适合关注 RealIP 行为变化、StartConfig.Listener、NewDefaultFS 和观测性中间件入口的开发团队。
- 必须用
language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))得到有序[]language.Tag - 白名单要从配置读取(如
supported_languages: ["en", "zh-Hans", "ja"]),转成[]language.Tag后只初始化一次matcher := language.NewMatcher(supported) - 匹配时调
matched, confidence := matcher.Match(acceptTags...),confidence == language.No就算失败,不能只看 bool - 全失败时 fallback 到配置里的
default_language(如en),而不是硬写language.English
怎么在 Echo 中每请求注入独立 Localizer
*i18n.Localizer 必须每请求新建,不能复用、不能缓存、不能塞进全局变量——并发下会语言错乱。
-
*i18n.Bundle是线程安全的,全局单例,在main()启动时一次性bundle.ParseFS加载全部active.*.json - Echo 中间件里解析出
matchedTag后,立刻调bundle.NewLocalizer(matchedTag),得到本次请求专用的localizer - 推荐通过闭包传参:把
localizer作为参数传给 handler 函数,而不是用c.Set("localizer", ...)或context.WithValue(类型不安全、易漏判) - 调用时用
localizer.Localize(&i18n.LocalizeConfig{MessageID: "auth.login.title"}),注意不是函数调用,是方法调用
最容易被忽略的是 JSON 文件里的 description 字段和文件名中连字符与大小写的严格性——active.zh-hans.json 和 active.zh-Hans.json 在某些文件系统下表现不同,但 go-i18n/v2 只认后者;description 缺失不会报错,但会导致整个 key 失效,debug 成本极高。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










