package langx import ( "fmt" "strings" ) // options 是库的全局配置。 // // 它是值类型, 一旦随快照发布就不再被修改: 任何配置变更都必须构造一份新的 options // 并通过 update/updateErr 原子替换, 这样读路径可以完全不加锁, 也不会与写入产生竞态。 type options struct { // defaultCode 是未知 key 返回的响应码。 // 默认 -1, 表示"未定义", 避免 key 拼写错误时静默返回成功码。 defaultCode int // defaultLang 是默认语言(已归一化), 兜底时使用。 defaultLang string // replaceKey 是占位符模式, 形如 "#%s#"。 replaceKey string // replacePrefix/replaceSuffix 由 replaceKey 推导, 避免每次替换都做一次 Sprintf。 replacePrefix string replaceSuffix string // missing 是译文缺失时的回调, 可为 nil。 missing func(lang, key string) } func defaultOptions() options { o := options{ defaultCode: -1, defaultLang: "zh", replaceKey: "#%s#", } o.derive() return o } // derive 由 replaceKey 推导出占位符的前缀与后缀 func (o *options) derive() { const probe = "\x00langx\x00" out := fmt.Sprintf(o.replaceKey, probe) i := strings.Index(out, probe) if i < 0 { o.replacePrefix, o.replaceSuffix = "", "" return } o.replacePrefix = out[:i] o.replaceSuffix = out[i+len(probe):] } func (o options) validate() error { if strings.TrimSpace(o.defaultLang) == "" { return fmt.Errorf("langx: 默认语言不能为空") } return validateReplaceKey(o.replaceKey) } // validateReplaceKey 要求模式中有且只有一个 %s func validateReplaceKey(pattern string) error { if pattern == "" { return fmt.Errorf("langx: replaceKey 不能为空, 形如 %q", "#%s#") } out := fmt.Sprintf(pattern, "x") if strings.Contains(out, "%!") || !strings.Contains(out, "x") { return fmt.Errorf("langx: replaceKey=%q 非法, 需要且只需要一个 %%s 占位符, 例如 %q", pattern, "#%s#") } return nil } // Option 是配置项, 通过 InitLangx 应用。 type Option func(*options) // InitLangx 应用配置项, 可重复调用, 后一次在前一次的配置上叠加。 // // 与旧版本的区别: 配置非法时返回错误, 且本次传入的所有 Option 都不会生效(不会写入一半)。 // 不需要错误处理时可以直接当语句使用: langx.InitLangx(...) func InitLangx(ops ...Option) error { return updateErr(func(old *state) (*state, error) { next := old.ops // 值拷贝, 不影响已经发布的配置 for _, opt := range ops { if opt == nil { continue } opt(&next) } if err := next.validate(); err != nil { return nil, err } next.derive() return setOptions(old, next), nil }) } // Reset 清空所有已注册的响应码与译文, 并恢复默认配置。便于测试与重新初始化。 func Reset() { update(func(*state) *state { return newState() }) } // SetDefaultCode 设置未知 key 返回的响应码, 默认 -1。 // // 如果 code 会被直接当作 HTTP 状态码使用, 建议设置为 500, 让缺失的 key 以失败收场。 func SetDefaultCode(code int) Option { return func(o *options) { o.defaultCode = code } } // SetDefaultLanguage 设置默认(兜底)语言, 默认 "zh"。 func SetDefaultLanguage(lang string) Option { return func(o *options) { o.defaultLang = normalizeLang(lang) } } // SetReplaceKey 设置占位符模式, 必须包含且只包含一个 %s, 默认 "#%s#"(即 #name#)。 func SetReplaceKey(key string) Option { return func(o *options) { o.replaceKey = key } } // SetMissingHandler 设置译文缺失时的回调, 便于记录漏翻的 key。传 nil 表示取消回调。 // // 回调在读取路径上同步执行, 且不在任何锁内, 因此可以安全地再次调用本库的函数。 func SetMissingHandler(fn func(lang, key string)) Option { return func(o *options) { o.missing = fn } }