登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go go-i18n 怎样从嵌入文件系统加载翻译

来源:17golang原创

时间:2026-10-30 21:34:07 172浏览 收藏

我第一次把 Go 服务做成单个可交付二进制时,最容易遗漏的不是翻译文本,而是部署后翻译文件的路径。go-i18n 已经提供了面向 fs.FS 的加载方法,配合 go:embed 就能把消息文件和程序一起打包。核心写法是:声明一个匹配翻译文件的 embed.FS,再把它传给 Bundle.LoadMessageFileFS。

官方资料:https://pkg.go.dev/github.com/nicksnyder/go-i18n/v2/i18n

要点速览
  • go:embed 只负责把文件放进程序,真正解析翻译的是 LoadMessageFileFS。
  • 文件名中的语言标签要和 go-i18n 的解析规则匹配,例如 locale.zh-CN.json。
  • 嵌入路径是 fs.FS 内部路径,不是部署机器上的绝对路径;加载失败先查文件是否被匹配。

先把翻译文件放进可预测的嵌入范围

这个小项目只做一件事:读取英文和简体中文消息,在命令行中根据语言偏好输出问候语。目录可以保持很小:

i18n-demo/
├── main.go
├── locale.en.json
└── locale.zh-CN.json

JSON 文件使用消息 ID 作为键,消息内容放在 other 字段。这里不需要给 JSON 加注释,字段含义直接在代码外说明:

{
  "HelloUser": {
    "description": "欢迎语",
    "other": "你好,{{.Name}}!"
  }
}

英文文件只需要把 other 换成 Hello, {{.Name}}!。文件名中的 zh-CN 和 en 会参与语言标签解析;不要把它们改成程序部署目录里的自定义别名。

Go go-i18n 中 go:embed、embed.FS、Bundle 和翻译文件之间关系的静态结构说明图
图1:结构说明图,展示翻译文件如何进入 embed.FS,再交给 Bundle 解析,不是运行截图。

用 LoadMessageFileFS 把 embed.FS 交给 Bundle

初始化阶段创建一个长期使用的 Bundle,然后分别加载两份文件。LoadMessageFileFS 的第二个参数是嵌入文件系统中的路径,所以这里传文件名,不传操作系统的绝对路径:

package main

import (
    "embed"
    "fmt"

    "github.com/nicksnyder/go-i18n/v2/i18n"
    "golang.org/x/text/language"
)

// LocaleFS 保存编译时匹配到的翻译文件,发布后不依赖外部目录。
//go:embed locale.*.json
var LocaleFS embed.FS

func newBundle() (*i18n.Bundle, error) {
    // 默认语言用于没有更好匹配结果时的兜底。
    bundle := i18n.NewBundle(language.English)

    // 路径相对于 embed.FS 根目录,不能写成本机绝对路径。
    for _, path := range []string{"locale.en.json", "locale.zh-CN.json"} {
        if _, err := bundle.LoadMessageFileFS(LocaleFS, path); err != nil {
            return nil, fmt.Errorf("加载翻译文件 %s: %w", path, err)
        }
    }
    return bundle, nil
}

这一步最值得记住的是职责分界:embed.FS 解决“文件在哪里”,Bundle 解决“消息如何解析和按语言组织”。如果把 LoadMessageFileFS 换回 LoadMessageFile,程序又会尝试读取宿主机文件系统,容器或单文件发布时就容易出现路径问题。

让 Localizer 根据语言偏好返回文本

有了 Bundle,业务代码只创建一个带语言偏好的 Localizer。它可以接收明确语言,也可以接收类似 HTTP Accept-Language 的偏好列表:

func main() {
    bundle, err := newBundle()
    if err != nil {
        // 初始化失败时直接退出,避免程序带着半套翻译继续运行。
        panic(err)
    }

    // 语言偏好可以来自命令行、请求头或用户设置。
    localizer := i18n.NewLocalizer(bundle, "zh-CN", "en-US")
    text := localizer.MustLocalize(&i18n.LocalizeConfig{
        MessageID: "HelloUser",
        TemplateData: map[string]string{
            "Name": "小林",
        },
    })
    fmt.Println(text)
}

这里的 MessageID 必须和 JSON 的键完全一致,TemplateData 中的 Name 则对应消息里的 {{.Name}}。若希望业务层自行处理缺失消息,可以用 Localize 接收错误;示例使用 MustLocalize,更适合初始化完成后明确认为消息一定存在的场景。

Go go-i18n 中语言偏好、Localizer、MessageID 与默认语言回退关系的静态说明图
图2:关系说明图,展示语言偏好如何进入 Localizer,再找到 MessageID 并在必要时回退,不是运行结果截图。

加载失败时先查这三个边界

现象优先检查原因
找不到文件go:embed 的匹配模式和第二个参数嵌入 FS 内没有该路径,或加载时多写了一层目录
语言没有命中文件名中的语言标签文件名解析出的标签与 Localizer 偏好不匹配
消息 ID 缺失JSON 顶层键和 MessageID翻译文件已加载,但没有对应消息

如果翻译文件放在子目录,例如 locales/locale.zh-CN.json,嵌入模式和加载路径也要保持一致://go:embed locales/*.json 对应 LoadMessageFileFS(LocaleFS, "locales/locale.zh-CN.json")。不要用本地开发机上的绝对路径掩盖这个差异。

这个小方案适合怎样的项目

对命令行工具、单体服务和容器部署来说,把少量稳定翻译和二进制一起发布很省心;镜像里不需要再维护一份容易漏挂载的语言目录。翻译数量大、需要运营人员热更新,或者语言包由独立团队频繁发布时,则应把加载边界留在外部存储,并为文件版本和回滚单独设计。

我的经验是:只要翻译文件属于程序版本的一部分,就优先使用 go:embed;只要翻译文件属于运营数据,就不要为了省一个挂载点把它硬塞进二进制。这个取舍比单纯记住某个 API 名称更重要。

常见问题

LoadMessageFileFS 能直接读取操作系统路径吗?

不能。它接收的是 fs.FS 和该文件系统内部的路径;操作系统路径应使用面向宿主文件系统的加载方法。

一定要使用 TOML 才能配合 go:embed 吗?

不需要。go-i18n 支持多种消息文件格式,本文用 JSON 是为了让嵌入路径和加载调用更直观;如果使用 TOML 等格式,应按对应格式注册解析函数。

默认语言是不是所有请求的强制语言?

不是。它是 Bundle 的默认语言,Localizer 仍会优先根据传入的语言偏好匹配已加载的语言标签。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>