增加多语言支持

This commit is contained in:
2025-12-07 10:32:36 +08:00
parent 684923f9cb
commit f8f4df4073
9 changed files with 1287 additions and 5 deletions

View File

@@ -5,11 +5,13 @@ import (
"fmt"
"io"
"net/http"
"strings"
"time"
"git.toowon.com/jimmy/go-common/config"
"git.toowon.com/jimmy/go-common/email"
commonhttp "git.toowon.com/jimmy/go-common/http"
"git.toowon.com/jimmy/go-common/i18n"
"git.toowon.com/jimmy/go-common/logger"
"git.toowon.com/jimmy/go-common/middleware"
"git.toowon.com/jimmy/go-common/migration"
@@ -118,6 +120,7 @@ type Factory struct {
sms *sms.SMS // 短信客户端(延迟初始化)
db *gorm.DB // 数据库连接(延迟初始化)
redis *redis.Client // Redis客户端延迟初始化
i18n *i18n.I18n // 国际化工具(延迟初始化)
}
// NewFactory 创建工厂实例
@@ -774,7 +777,10 @@ func (f *Factory) GetMiddlewareChain() *middleware.Chain {
middlewares = append(middlewares, middleware.CORS(corsConfig))
}
// 5. Timezone 中间件(必需,处理时区
// 5. Language 中间件(必需,处理语言
middlewares = append(middlewares, middleware.Language)
// 6. Timezone 中间件(必需,处理时区)
middlewares = append(middlewares, middleware.Timezone)
return middleware.NewChain(middlewares...)
@@ -911,10 +917,69 @@ func (f *Factory) SuccessPage(w http.ResponseWriter, list interface{}, total int
// Error 错误响应(黑盒模式,推荐使用)
// w: ResponseWriter
// code: 业务错误码非0表示业务错误
// message: 错误消息
func (f *Factory) Error(w http.ResponseWriter, code int, message string) {
commonhttp.Error(w, code, message)
// r: HTTP请求用于获取语言信息和i18n处理
// code: 业务错误码如果message是消息代码此参数会被语言文件中的code覆盖
// message: 错误消息或消息代码如果i18n已初始化且message是消息代码格式会自动获取国际化消息和业务code
// args: 可选参数,用于格式化消息(类似 fmt.Sprintf仅在message是消息代码时使用
//
// 使用逻辑:
// 1. 如果i18n已初始化且message看起来是消息代码包含点号如 "user.not_found"
// 则从请求context中获取语言并尝试从语言文件中获取国际化消息和业务code
// 2. 如果获取到国际化消息使用语言文件中的code作为响应code使用国际化消息作为响应message
// 3. 如果未获取到或i18n未初始化使用传入的code和message
//
// 示例:
//
// // 方式1直接传入消息代码推荐自动国际化
// fac.Error(w, r, 0, "user.not_found")
// // 如果请求语言是 zh-CN且语言文件中 "user.not_found" 的 code 是 1001
// // 返回: {"code": 1001, "message": "用户不存在"}
// // 如果请求语言是 en-US返回: {"code": 1001, "message": "User not found"}
//
// // 方式2带参数的消息代码
// fac.Error(w, r, 0, "user.welcome", "Alice")
// // 如果消息内容是 "欢迎,%s",返回: {"code": 0, "message": "欢迎Alice"}
//
// // 方式3直接传入消息文本不使用国际化
// fac.Error(w, r, 500, "系统错误")
// // 返回: {"code": 500, "message": "系统错误"}
func (f *Factory) Error(w http.ResponseWriter, r *http.Request, code int, message string, args ...interface{}) {
// 判断message是否是消息代码简单判断包含点号
isMessageCode := strings.Contains(message, ".")
var finalCode int
var finalMessage string
if isMessageCode {
// 尝试从i18n获取国际化消息和业务code
if i, err := f.getI18n(); err == nil {
// i18n已初始化获取语言并查找消息
lang := f.GetLanguage(r)
if lang == "" {
lang = i.GetDefaultLang()
}
msgInfo := i.GetMessageInfo(lang, message, args...)
// 如果获取到了国际化消息不是返回code本身使用国际化消息和业务code
if msgInfo.Message != message {
finalCode = msgInfo.Code
finalMessage = msgInfo.Message
} else {
// 消息代码不存在使用传入的code和消息代码作为消息
finalCode = code
finalMessage = message
}
} else {
// i18n未初始化使用传入的code和消息代码作为消息
finalCode = code
finalMessage = message
}
} else {
// 不是消息代码格式使用传入的code和消息
finalCode = code
finalMessage = message
}
commonhttp.Error(w, finalCode, finalMessage)
}
// SystemError 系统错误响应返回HTTP 500黑盒模式推荐使用
@@ -1010,6 +1075,14 @@ func (f *Factory) GetTimezone(r *http.Request) string {
return commonhttp.GetTimezone(r)
}
// GetLanguage 从请求的context中获取语言黑盒模式推荐使用
// r: HTTP请求
// 如果使用了middleware.Language中间件可以从context中获取语言信息
// 如果未设置,返回默认语言 zh-CN
func (f *Factory) GetLanguage(r *http.Request) string {
return commonhttp.GetLanguage(r)
}
// ========== Tools工具方法黑盒模式推荐使用 ==========
//
// 这些方法直接调用 tools 包的公共方法,保持低耦合。
@@ -1355,3 +1428,128 @@ func (f *Factory) GenerateRefundNo() string {
func (f *Factory) GenerateTransferNo() string {
return tools.GenerateTransferNo()
}
// ========== I18n 国际化工具(黑盒模式,推荐使用) ==========
//
// 这些方法提供多语言内容管理功能,支持从文件加载语言内容,通过语言代码和消息代码获取对应语言的内容。
// getI18n 获取国际化工具实例(内部方法,延迟初始化)
func (f *Factory) getI18n() (*i18n.I18n, error) {
if f.i18n != nil {
return f.i18n, nil
}
// 如果没有配置,返回错误
return nil, fmt.Errorf("i18n not initialized, please call InitI18n first")
}
// InitI18n 初始化国际化工具(黑盒模式,推荐使用)
// defaultLang: 默认语言代码(如 "zh-CN", "en-US"
// 初始化后可以调用 LoadI18nFromDir 或 LoadI18nFromFile 加载语言文件
//
// 示例:
//
// fac, _ := factory.NewFactoryFromFile("config.json")
// fac.InitI18n("zh-CN")
// fac.LoadI18nFromDir("locales")
func (f *Factory) InitI18n(defaultLang string) {
f.i18n = i18n.NewI18n(defaultLang)
}
// LoadI18nFromDir 从目录加载多个语言文件(黑盒模式,推荐使用)
// dirPath: 语言文件目录路径
// 文件命名规则:{语言代码}.json如 zh-CN.json, en-US.json
//
// 文件格式示例zh-CN.json
//
// {
// "user.not_found": "用户不存在",
// "user.login_success": "登录成功",
// "user.welcome": "欢迎,%s"
// }
//
// 示例:
//
// fac.InitI18n("zh-CN")
// fac.LoadI18nFromDir("locales")
func (f *Factory) LoadI18nFromDir(dirPath string) error {
i, err := f.getI18n()
if err != nil {
return err
}
return i.LoadFromDir(dirPath)
}
// LoadI18nFromFile 从单个语言文件加载内容(黑盒模式,推荐使用)
// filePath: 语言文件路径JSON格式
// lang: 语言代码(如 "zh-CN", "en-US"
//
// 示例:
//
// fac.InitI18n("zh-CN")
// fac.LoadI18nFromFile("locales/zh-CN.json", "zh-CN")
// fac.LoadI18nFromFile("locales/en-US.json", "en-US")
func (f *Factory) LoadI18nFromFile(filePath, lang string) error {
i, err := f.getI18n()
if err != nil {
return err
}
return i.LoadFromFile(filePath, lang)
}
// GetMessage 获取指定语言和代码的消息内容(黑盒模式,推荐使用)
// lang: 语言代码(如 "zh-CN", "en-US"
// code: 消息代码(如 "user.not_found"
// args: 可选参数,用于格式化消息(类似 fmt.Sprintf
//
// 返回逻辑:
// 1. 如果指定语言存在该code返回对应内容
// 2. 如果指定语言不存在,尝试使用默认语言
// 3. 如果默认语言也不存在返回code本身作为fallback
//
// 示例:
//
// // 简单消息
// msg := fac.GetMessage("zh-CN", "user.not_found")
// // 返回: "用户不存在"
//
// // 带参数的消息
// msg := fac.GetMessage("zh-CN", "user.welcome", "Alice")
// // 如果消息内容是 "欢迎,%s",返回: "欢迎Alice"
func (f *Factory) GetMessage(lang, code string, args ...interface{}) string {
i, err := f.getI18n()
if err != nil {
// 如果未初始化返回code本身
return code
}
return i.GetMessage(lang, code, args...)
}
// GetI18n 获取国际化工具对象(高级功能时使用)
// 返回已初始化的国际化工具对象
//
// 推荐使用黑盒方法:
// - GetMessage():获取消息内容
// - LoadI18nFromDir():加载语言文件目录
// - LoadI18nFromFile():加载单个语言文件
//
// 仅在需要使用高级功能时获取对象:
// - HasLang():检查语言是否存在
// - GetSupportedLangs():获取所有支持的语言
// - ReloadFromFile():重新加载语言文件
// - SetDefaultLang():动态设置默认语言
//
// 示例(常用操作,推荐):
//
// fac.InitI18n("zh-CN")
// fac.LoadI18nFromDir("locales")
// msg := fac.GetMessage("zh-CN", "user.not_found")
//
// 示例(高级功能):
//
// i18n, _ := fac.GetI18n()
// langs := i18n.GetSupportedLangs()
// hasLang := i18n.HasLang("en-US")
func (f *Factory) GetI18n() (*i18n.I18n, error) {
return f.getI18n()
}