Go 实战
Gin 框架轻松集成 i18n 国际化
使用 go-i18n + gin-contrib/i18n + embed.FS,
十分钟为你的 API 添加完整的多语言支持。
最终效果
bash
# 不传语言头 → 默认中文 $ curl http://localhost:8080/ping {"message": "欢迎使用"} # 传 x-lang: en → 英文 $ curl -H "x-lang: en" http://localhost:8080/ping {"message": "Welcome"} # 错误场景同样国际化 $ curl -H "x-lang: en" http://localhost:8080/login {"code": 400, "message": "Invalid email format"}
1. 安装依赖
bash
go get github.com/gin-contrib/i18n go get github.com/nicksnyder/go-i18n/v2 go get github.com/pelletier/go-toml/v2
gin-contrib/i18n
Gin 中间件封装
go-i18n/v2
核心国际化引擎
go-toml/v2
TOML 翻译文件解析
2. 目录结构
text
project/
├── main.go
└── i18n/
├── i18n.go // 中间件 + 消息获取
├── keys.go // 翻译 Key 常量
└── locales/
├── zh.toml // 中文(默认)
└── en.toml // 英文
3. 编写翻译文件
翻译文件使用 TOML 格式,比 JSON 更可读,支持注释。每条消息以 [messageID] 定义,other 为默认复数形式。
locales/zh.toml
# 通用 [welcome] other = "欢迎使用" [paramError] other = "参数不合法" [notFound] other = "资源不存在" # 认证相关 [invalidEmail] other = "邮箱格式不正确" [loginSuccess] other = "登录成功" # 带参数的模板消息 [greeting] other = "你好,{{.Name}}!"
locales/en.toml
# Common [welcome] other = "Welcome" [paramError] other = "Invalid parameters" [notFound] other = "Resource not found" # Auth [invalidEmail] other = "Invalid email format" [loginSuccess] other = "Login successful" # Template message with params [greeting] other = "Hello, {{.Name}}!"
4. 定义 Key 常量
将翻译 Key 定义为 Go 常量,避免硬编码字符串,拼写错误在编译期即可发现。
Go
// i18n/keys.go package i18n const ( Welcome = "welcome" ParamError = "paramError" NotFound = "notFound" InvalidEmail = "invalidEmail" LoginSuccess = "loginSuccess" Greeting = "greeting" )
为什么用常量?
— 当你写
i18n.InvalidEmail 拼错时编译器直接报错,而字符串 "invaldEmail" 只会在运行时默默返回 Key 本身。
5. 核心代码实现
i18n/i18n.go — 中间件 + 工具函数
Go
package i18n import ( "embed" "path/filepath" "github.com/gin-contrib/i18n" "github.com/gin-gonic/gin" goi18n "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/pelletier/go-toml/v2" "golang.org/x/text/language" ) // 编译时嵌入 locales 目录,部署时无需携带额外文件 //go:embed locales var locales embed.FS // langLoader 适配 embed.FS 到 i18n Loader 接口 type langLoader struct{} func (l langLoader) LoadMessage(path string) ([]byte, error) { return locales.ReadFile(filepath.Join("locales", path)) } // Middleware 返回 Gin i18n 中间件 func Middleware() gin.HandlerFunc { return i18n.Localize( i18n.WithBundle(&i18n.BundleCfg{ AcceptLanguage: []language.Tag{language.Chinese, language.English}, DefaultLanguage: language.Chinese, FormatBundleFile: "toml", UnmarshalFunc: toml.Unmarshal, Loader: &langLoader{}, }), // 自定义语言检测:从 x-lang 请求头读取 i18n.WithGetLngHandle(func(c *gin.Context, defaultLng string) string { if lang := c.GetHeader("x-lang"); lang != "" { return lang } return defaultLng }), ) } // GetMsg 获取当前请求语言的翻译文本 func GetMsg(ctx *gin.Context, msgId string) string { msg, err := i18n.GetMessage(ctx, msgId) if err != nil { return msgId // 降级:返回 Key 本身 } return msg } // GetMsgWithParam 获取带参数的翻译文本 func GetMsgWithParam(ctx *gin.Context, msgId string, data map[string]any) string { msg, err := i18n.GetMessage(ctx, goi18n.LocalizeConfig{ MessageID: msgId, TemplateData: data, }) if err != nil { return msgId } return msg }
6. 注册中间件 & 业务调用
Go
// main.go package main import ( "net/http" "your-project/i18n" "github.com/gin-gonic/gin" ) func main() { r := gin.Default() // 注册 i18n 中间件(放在最前面) r.Use(i18n.Middleware()) // 简单消息 r.GET("/ping", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": i18n.GetMsg(c, i18n.Welcome), }) }) // 带参数的模板消息 r.GET("/hello", func(c *gin.Context) { name := c.Query("name") c.JSON(http.StatusOK, gin.H{ "message": i18n.GetMsgWithParam(c, i18n.Greeting, map[string]any{ "Name": name, }), }) }) // 错误响应国际化 r.POST("/login", func(c *gin.Context) { email := c.PostForm("email") if !isValidEmail(email) { c.JSON(http.StatusBadRequest, gin.H{ "code": 400, "message": i18n.GetMsg(c, i18n.InvalidEmail), }) return } // ... }) r.Run(":8080") }
7. 请求处理流程
1
客户端发起请求
携带 x-lang: en Header(可选,缺省为中文)
2
i18n 中间件拦截
解析 x-lang 值,匹配最接近的语言标签,创建 Localizer 存入 Context
3
Handler / Service 调用 GetMsg
从 Context 中取出 Localizer,查找对应语言的 TOML 翻译条目
4
返回本地化响应
x-lang: en → "Invalid email format" |
x-lang: zh → "邮箱格式不正确"
8. 设计要点
方案亮点
- embed.FS 嵌入 — 翻译文件编译进二进制,部署零外部文件依赖
- 常量化 Key — 编译期发现拼写错误,IDE 自动补全友好
- 请求级隔离 — 每个请求独立语言上下文,天然并发安全
- 优雅降级 — 翻译缺失返回 Key 本身,服务不会崩溃
- TOML 格式 — 支持注释,比 JSON 更适合翻译人员维护
- 模板消息 — 内置 Go template 语法,支持动态参数
实践建议
- 中间件放在 第一个 注册,确保后续中间件也能使用 i18n
- Key 命名用 camelCase,与 Go 常量风格保持一致
- 按模块分组 Key 常量(auth、user、order…),文件大了可拆分
- 新增 Key 时同步更新所有 .toml 文件,避免部分语言缺失翻译
-
Header 名选用自定义的
x-lang,避免解析复杂的Accept-Language -
需要新语言?只需添加
.toml文件 +AcceptLanguage列表即可
9. 扩展新语言只需两步
1
新增翻译文件
toml
# locales/ja.toml [welcome] other = "ようこそ" [paramError] other = "パラメータが不正です" # ... 其他条目
2
注册语言标签
Go
AcceptLanguage: []language.Tag{
language.Chinese,
language.English,
language.Japanese, // ← 加上这行
},
无需修改任何业务代码,embed.FS 会自动包含新文件,客户端传 x-lang: ja 即可生效。
总结
三个文件
i18n.go + keys.go + .toml 翻译文件,全部代码不到 60 行
零运行时依赖
embed.FS 将翻译编译进二进制,单文件部署即可
零侵入接入
一行
r.Use(i18n.Middleware()) 即可,业务代码改动极小