Go多语言核心是约束语言白名单、fallback行为、资源路径三危险点;配置需含default_language、supported_languages、locales_path;Accept-Language须用language.ParseAcceptLanguage解析;文件名必须为active.zh-CN.json且JSON含description字段;Bundle全局单例,Localizer每请求新建。

Go 框架做多语言,核心不是“配个 config 就能切”,而是配置必须约束住三个危险点:语言白名单、fallback 行为、资源加载路径。配错任意一项,localizer.Localize 就会静默返回原始 key,线上查不出原因。
配置文件必须定义 supported_languages 和 default_language
硬编码语言列表会让运维无法热更新支持语种,改个语言就得发版。viper 加载的 config.yaml 至少要包含:
-
default_language: "zh-CN"—— 必须是language.Make("zh-CN")可解析的 BCP 47 标签,不能写zh_CN或chinese -
supported_languages: ["en", "zh-CN", "ja", "ko"]—— 字符串数组,后续传给language.NewMatcher构建匹配器 -
locales_path: "./locales"—— 要和os.DirFS("./locales")的路径完全一致,不能含..或多余斜杠
Accept-Language 解析必须走 language.ParseAcceptLanguage + matcher.Match
浏览器 Header 的 Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8 不是字符串列表,手动 strings.Split 会丢权重、错判变体(比如把 zh-Hans 当作不兼容),最终 fallback 到默认语言还查不出为什么。
- 用
language.ParseAcceptLanguage(r.Header.Get("Accept-Language"))得到已排序、标准化的[]language.Tag - 用配置里的
supported_languages初始化matcher := language.NewMatcher(supportedTags) - 遍历解析结果,对每个
tag调matcher.Match(tag),取第一个confidence != language.No的结果 - 全失败才 fallback 到
config.default_language,别写死en-US
Bundle 加载路径和文件名必须和配置严格对齐
bundle.LoadMessageFile 静默失败是 Go 多语言最常见坑:不 panic、不报错、只返回空字符串或原始 key。根本原因是路径或命名没踩中 go-i18n/v2 的硬规范。
立即学习“go语言免费学习笔记(深入)”;
- 文件名必须是
active.zh-CN.json(不是zh.json、zh_CN.json、cn.json) - 路径必须匹配
os.DirFS(config.locales_path),比如config.locales_path == "./locales",那文件就得在./locales/active.zh-CN.json - JSON 结构必须含
description和translation字段:{"login.title": {"description": "page title", "translation": "登录"}} - 只加载
active.*.json,inactive.*.json被设计为自动跳过
Localizer 必须每请求新建,但 Bundle 必须全局单例
复用 *i18n.Localizer 会导致并发请求语言错乱;而每次请求都重载 *i18n.Bundle 会浪费 CPU 且破坏热重载能力。
-
*i18n.Bundle全局初始化一次,在启动时调bundle.LoadMessageFile加载所有active.*.json - 每个 HTTP 请求拿到匹配后的
language.Tag后,立刻调bundle.NewLocalizer(tag) - 别缓存
*i18n.Localizer实例,它轻量(内部只是 map 查找),但非 goroutine-safe - 中间件里生成后,建议通过闭包或 struct receiver 传入 handler,别塞进
context.WithValue(类型不安全、易漏)
最容易被忽略的是:JSON 文件里缺 description 字段、locales_path 配置和实际 fs 路径差一层目录、或者 matcher.Match 返回的 confidence 没判断就直接用了——这三处出错,Localize 都不会报错,只会默默交给你原始 key。


















