# 国际化工具文档 ## 概述 国际化工具(i18n)提供多语言内容管理功能,支持从文件加载语言内容,通过语言代码和消息代码获取对应语言的内容。 ## 功能特性 - **多语言支持**:支持加载多个语言文件 - **文件加载**:支持从目录或单个文件加载语言内容 - **语言回退**:当指定语言不存在时,自动回退到默认语言 - **参数替换**:支持在消息中使用格式化参数(类似 fmt.Sprintf) - **并发安全**:使用读写锁保证并发安全 - **动态加载**:支持重新加载语言文件 ## 快速开始 ### 1. 创建语言文件 创建语言文件目录 `locales/`,并创建对应的语言文件: **locales/zh-CN.json**: ```json { "user.not_found": { "code": 100002, "message": "用户不存在" }, "user.login_success": { "code": 0, "message": "登录成功" }, "user.welcome": { "code": 0, "message": "欢迎,%s" }, "error.invalid_params": { "code": 100001, "message": "参数无效" } } ``` **locales/en-US.json**: ```json { "user.not_found": { "code": 100002, "message": "User not found" }, "user.login_success": { "code": 0, "message": "Login successful" }, "user.welcome": { "code": 0, "message": "Welcome, %s" }, "error.invalid_params": { "code": 100001, "message": "Invalid parameters" } } ``` ### 2. 初始化并使用 ```go package main import ( "git.toowon.com/jimmy/go-common/factory" ) func main() { // 创建工厂实例 fac, _ := factory.NewFactoryFromFile("config.json") // 初始化国际化工具,设置默认语言为中文 fac.InitI18n("zh-CN") // 从目录加载所有语言文件 fac.LoadI18nFromDir("locales") // 获取消息 msg := fac.GetMessage("zh-CN", "user.not_found") // 返回: "用户不存在" // 带参数的消息 msg = fac.GetMessage("zh-CN", "user.welcome", "Alice") // 返回: "欢迎,Alice" // 在HTTP handler中使用Error方法(推荐) // fac.Error(w, r, 0, "user.not_found") // 会自动从语言文件获取业务code和国际化消息 // 返回: {"code": 100002, "message": "用户不存在"} } ``` ## API 文档 ### 初始化方法 #### InitI18n 初始化国际化工具,设置默认语言。 ```go fac.InitI18n(defaultLang string) ``` **参数**: - `defaultLang`: 默认语言代码(如 "zh-CN", "en-US") **示例**: ```go fac.InitI18n("zh-CN") ``` ### 加载方法 #### LoadI18nFromDir 从目录加载多个语言文件(推荐方式)。 ```go fac.LoadI18nFromDir(dirPath string) error ``` **参数**: - `dirPath`: 语言文件目录路径 **文件命名规则**: - 文件必须以 `.json` 结尾 - 文件名(去掉 `.json` 后缀)作为语言代码 - 例如:`zh-CN.json` 对应语言代码 `zh-CN` **示例**: ```go fac.LoadI18nFromDir("locales") ``` #### LoadI18nFromFile 从单个语言文件加载内容。 ```go fac.LoadI18nFromFile(filePath, lang string) error ``` **参数**: - `filePath`: 语言文件路径(JSON格式) - `lang`: 语言代码(如 "zh-CN", "en-US") **示例**: ```go fac.LoadI18nFromFile("locales/zh-CN.json", "zh-CN") fac.LoadI18nFromFile("locales/en-US.json", "en-US") ``` ### 错误响应方法 #### Error 错误响应(自动国际化,推荐使用)。 ```go fac.Error(w, r, code int, message string, args ...interface{}) ``` **参数**: - `w`: ResponseWriter - `r`: HTTP请求(用于获取语言信息) - `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 **示例**: ```go // 方式1:传入消息代码(推荐,自动获取业务code和国际化消息) fac.Error(w, r, 0, "user.not_found") // 如果请求语言是 zh-CN,且语言文件中 "user.not_found" 的 code 是 100002, // 返回: {"code": 100002, "message": "用户不存在"} // 如果请求语言是 en-US,返回: {"code": 100002, "message": "User not found"} // 注意:业务code在所有语言中保持一致 // 方式2:带参数的消息代码 fac.Error(w, r, 0, "user.welcome", "Alice") // 如果消息内容是 "欢迎,%s",返回: {"code": 0, "message": "欢迎,Alice"} // 方式3:直接传入消息文本(不使用国际化) fac.Error(w, r, 500, "系统错误") // 返回: {"code": 500, "message": "系统错误"} ``` ### 获取消息方法 #### GetMessage 获取指定语言和代码的消息内容(推荐使用)。 ```go fac.GetMessage(lang, code string, args ...interface{}) string ``` **参数**: - `lang`: 语言代码(如 "zh-CN", "en-US") - `code`: 消息代码(如 "user.not_found") - `args`: 可选参数,用于格式化消息(类似 fmt.Sprintf) **返回逻辑**: 1. 如果指定语言存在该code,返回对应内容 2. 如果指定语言不存在,尝试使用默认语言 3. 如果默认语言也不存在,返回code本身(作为fallback) **示例**: ```go // 简单消息 msg := fac.GetMessage("zh-CN", "user.not_found") // 返回: "用户不存在" // 带参数的消息 msg := fac.GetMessage("zh-CN", "user.welcome", "Alice") // 如果消息内容是 "欢迎,%s",返回: "欢迎,Alice" ``` ### GetLanguage 从请求的context中获取语言代码(推荐使用)。 ```go fac.GetLanguage(r *http.Request) string ``` **参数**: - `r`: HTTP请求 **返回逻辑**: 1. 从请求context中获取语言(由middleware.Language中间件设置) 2. 如果未设置,返回默认语言 zh-CN **示例**: ```go lang := fac.GetLanguage(r) // 可能返回: "zh-CN", "en-US" 等 ``` ### 高级功能 #### GetI18n 获取国际化工具对象(仅在需要高级功能时使用)。 ```go fac.GetI18n() (*i18n.I18n, error) ``` **返回**:国际化工具对象 **高级功能示例**: ```go i18n, _ := fac.GetI18n() // 检查语言是否存在 hasLang := i18n.HasLang("en-US") // 获取所有支持的语言 langs := i18n.GetSupportedLangs() // 重新加载语言文件 i18n.ReloadFromFile("locales/zh-CN.json", "zh-CN") // 动态设置默认语言 i18n.SetDefaultLang("en-US") ``` ## 使用场景 ### 场景1:HTTP API 响应消息(推荐使用 Error 方法) **推荐方式**:使用 `Error` 方法,自动从请求中获取语言并返回国际化消息和业务code: ```go func handleLogin(w http.ResponseWriter, r *http.Request) { fac, _ := factory.NewFactoryFromFile("config.json") // 验证用户 user, err := validateUser(r) if err != nil { // 直接传入消息代码,自动获取业务code和国际化消息(推荐) // 会自动从请求context获取语言(由middleware.Language中间件设置) fac.Error(w, r, 0, "user.not_found") // 返回: {"code": 100002, "message": "用户不存在"} 或 {"code": 100002, "message": "User not found"} return } // 成功消息 lang := fac.GetLanguage(r) msg := fac.GetMessage(lang, "user.login_success") fac.Success(w, user, msg) } ``` ### 场景2:带参数的消息 ```go func handleWelcome(w http.ResponseWriter, r *http.Request) { fac, _ := factory.NewFactoryFromFile("config.json") lang := getLangFromRequest(r) username := "Alice" // 消息内容: "欢迎,%s" msg := fac.GetMessage(lang, "user.welcome", username) // 返回: "欢迎,Alice" 或 "Welcome, Alice" fac.Success(w, nil, msg) } ``` ### 场景3:错误消息国际化(推荐使用 Error 方法) **推荐方式**:使用 `Error` 方法,自动获取业务code和国际化消息: ```go func handleError(w http.ResponseWriter, r *http.Request, messageCode string) { fac, _ := factory.NewFactoryFromFile("config.json") // 直接传入消息代码,自动获取业务code和国际化消息(推荐) // 会自动从请求context获取语言并查找对应的国际化消息和业务code fac.Error(w, r, 0, "error."+messageCode) // 例如:fac.Error(w, r, 0, "error.invalid_params") // 返回: {"code": 100001, "message": "参数无效"} 或 {"code": 100001, "message": "Invalid parameters"} } ``` ## 语言文件格式 语言文件必须是 JSON 格式,每个消息包含业务code和消息内容: ```json { "user.not_found": { "code": 100002, "message": "用户不存在" }, "user.login_success": { "code": 0, "message": "登录成功" }, "user.welcome": { "code": 0, "message": "欢迎,%s" }, "error.invalid_params": { "code": 100001, "message": "参数无效" } } ``` **注意事项**: - key(消息代码)建议使用点号分隔的层级结构(如 `user.not_found`) - value 是一个对象,包含: - `code`: 业务错误码(整数),用于业务逻辑判断,所有语言的同一消息代码应使用相同的code - `message`: 消息内容(字符串),支持格式化占位符(如 `%s`, `%d`) - 所有语言文件应该包含相同的 key,确保所有语言都有对应的翻译 - **重要**:同一消息代码在所有语言文件中的 `code` 必须保持一致,只有 `message` 会根据语言变化 ## 最佳实践 ### 1. 消息代码命名规范 建议使用层级结构,便于管理和查找: ``` 模块.功能.状态 例如: - user.not_found - user.login_success - order.created - order.paid - error.invalid_params - error.server_error ``` ### 2. 默认语言设置 建议将最常用的语言设置为默认语言,确保在语言不存在时有合理的回退: ```go fac.InitI18n("zh-CN") // 中文作为默认语言 ``` ### 3. 语言代码规范 建议使用标准的语言代码格式: - `zh-CN`: 简体中文 - `zh-TW`: 繁体中文 - `en-US`: 美式英语 - `en-GB`: 英式英语 - `ja-JP`: 日语 - `ko-KR`: 韩语 ### 4. 文件组织 建议将所有语言文件放在统一的目录下: ``` project/ locales/ zh-CN.json en-US.json ja-JP.json ``` ### 5. 参数使用 对于需要动态内容的消息,使用格式化参数: ```json { "user.welcome": { "code": 0, "message": "欢迎,%s" }, "order.total": { "code": 0, "message": "订单总额:%.2f 元" }, "message.count": { "code": 0, "message": "您有 %d 条新消息" } } ``` 使用方式: ```go // 使用 GetMessage msg := fac.GetMessage("zh-CN", "user.welcome", "Alice") msg := fac.GetMessage("zh-CN", "order.total", 99.99) msg := fac.GetMessage("zh-CN", "message.count", 5) // 使用 Error 方法(推荐) fac.Error(w, r, 0, "user.welcome", "Alice") fac.Error(w, r, 0, "message.count", 5) ``` ### 6. 业务Code管理 **重要原则**:同一消息代码在所有语言文件中的业务code必须保持一致。 ```json // zh-CN.json { "user.not_found": { "code": 100002, // 业务code "message": "用户不存在" } } // en-US.json { "user.not_found": { "code": 100002, // 必须与zh-CN.json中的code相同 "message": "User not found" // 只有message会根据语言变化 } } ``` 这样设计的好处: - 调用端可以根据返回的 `code` 进行业务逻辑判断,不受语言影响 - 所有语言的同一错误使用相同的业务code,便于统一管理 ## 常见问题 ### Q1: 如果语言文件不存在会怎样? A: 如果语言文件不存在,`LoadI18nFromFile` 或 `LoadI18nFromDir` 会返回错误。建议在初始化时检查错误。 ### Q2: 如果消息代码不存在会怎样? A: `GetMessage` 和 `Error` 会按以下顺序查找: 1. 指定语言的消息 2. 默认语言的消息 3. 如果都不存在: - `GetMessage` 返回消息代码本身(作为fallback) - `Error` 使用传入的code参数和消息代码作为message ### Q5: 业务code在不同语言中必须相同吗? A: **是的,必须相同**。同一消息代码在所有语言文件中的业务code必须保持一致,只有message内容会根据语言变化。这样调用端可以根据code进行业务逻辑判断,不受语言影响。 ### Q3: 如何支持动态加载语言文件? A: 可以使用 `GetI18n()` 获取对象,然后调用 `ReloadFromFile()` 或 `ReloadFromDir()` 方法。 ### Q4: 是否支持嵌套的消息结构? A: 当前版本只支持扁平结构(key-value),不支持嵌套。如果需要嵌套结构,建议使用点号分隔的层级命名(如 `user.profile.name`)。 ## 完整示例 参考 [examples/i18n_example.go](../examples/i18n_example.go)