From 215e082028ed003c3c901d82bde2b1f3649205ec Mon Sep 17 00:00:00 2001 From: krau <71133316+krau@users.noreply.github.com> Date: Wed, 27 Aug 2025 11:09:38 +0800 Subject: [PATCH] feat: implement internationalization support and update help commands --- client/bot/handlers/help.go | 21 ++------ client/bot/handlers/save.go | 6 ++- client/bot/handlers/utils/msgelem/save.go | 15 ------ client/bot/handlers/utils/msgelem/watch.go | 19 ------- client/bot/handlers/watch.go | 5 +- cmd/geni18n/main.go | 51 ++++++++++++------ cmd/run.go | 29 +++++----- cmd/version.go | 2 +- common/i18n/i18n.go | 27 +++++----- common/i18n/i18nk/keys.go | 35 ++++++------ common/i18n/locale/zh-Hans.toml | 28 ---------- common/i18n/locale/zh-Hans.yaml | 62 ++++++++++++++++++++++ config/viper.go | 17 +++--- go.mod | 1 + go.sum | 2 + main.go | 2 + 16 files changed, 173 insertions(+), 149 deletions(-) delete mode 100644 client/bot/handlers/utils/msgelem/save.go delete mode 100644 client/bot/handlers/utils/msgelem/watch.go delete mode 100644 common/i18n/locale/zh-Hans.toml create mode 100644 common/i18n/locale/zh-Hans.yaml diff --git a/client/bot/handlers/help.go b/client/bot/handlers/help.go index d656891..0f18e3b 100644 --- a/client/bot/handlers/help.go +++ b/client/bot/handlers/help.go @@ -5,31 +5,16 @@ import ( "github.com/celestix/gotgproto/dispatcher" "github.com/celestix/gotgproto/ext" + "github.com/krau/SaveAny-Bot/common/i18n" + "github.com/krau/SaveAny-Bot/common/i18n/i18nk" "github.com/krau/SaveAny-Bot/config" ) func handleHelpCmd(ctx *ext.Context, update *ext.Update) error { - const helpText string = ` -Save Any Bot - 转存你的 Telegram 文件 -版本: %s , 提交: %s - -命令: -/start - 开始使用 -/help - 显示帮助 -/silent - 开关静默模式 -/storage - 设置默认存储位置 -/save [自定义文件名] - 保存文件 -/dir - 管理存储目录 -/rule - 管理规则 -/update - 检查更新并升级 - -使用帮助: https://sabot.unv.app/usage -反馈群组: https://t.me/ProjectSaveAny -` shortHash := config.GitCommit if len(shortHash) > 7 { shortHash = shortHash[:7] } - ctx.Reply(update, ext.ReplyTextString(fmt.Sprintf(helpText, config.Version, shortHash)), nil) + ctx.Reply(update, ext.ReplyTextString(fmt.Sprintf(i18n.T(i18nk.BotMsgHelpTextFmt), config.Version, shortHash)), nil) return dispatcher.EndGroups } diff --git a/client/bot/handlers/save.go b/client/bot/handlers/save.go index b0b8965..a4a4d90 100644 --- a/client/bot/handlers/save.go +++ b/client/bot/handlers/save.go @@ -12,6 +12,8 @@ import ( "github.com/krau/SaveAny-Bot/client/bot/handlers/utils/mediautil" "github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem" "github.com/krau/SaveAny-Bot/client/bot/handlers/utils/shortcut" + "github.com/krau/SaveAny-Bot/common/i18n" + "github.com/krau/SaveAny-Bot/common/i18n/i18nk" "github.com/krau/SaveAny-Bot/common/utils/strutil" "github.com/krau/SaveAny-Bot/common/utils/tgutil" "github.com/krau/SaveAny-Bot/pkg/tcbdata" @@ -28,7 +30,7 @@ func handleSaveCmd(ctx *ext.Context, update *ext.Update) error { } replyTo := update.EffectiveMessage.ReplyToMessage if replyTo == nil || replyTo.Message == nil { - ctx.Reply(update, ext.ReplyTextString(msgelem.SaveHelpText), nil) + ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgSaveHelpText)), nil) return dispatcher.EndGroups } genFilename := func() string { @@ -72,7 +74,7 @@ func handleSilentSaveReplied(ctx *ext.Context, update *ext.Update) error { } replyTo := update.EffectiveMessage.ReplyToMessage if replyTo == nil || replyTo.Message == nil { - ctx.Reply(update, ext.ReplyTextString(msgelem.SaveHelpText), nil) + ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgSaveHelpText)), nil) return dispatcher.EndGroups } genFilename := func() string { diff --git a/client/bot/handlers/utils/msgelem/save.go b/client/bot/handlers/utils/msgelem/save.go deleted file mode 100644 index 9d08d00..0000000 --- a/client/bot/handlers/utils/msgelem/save.go +++ /dev/null @@ -1,15 +0,0 @@ -package msgelem - -const ( - SaveHelpText = ` - 使用方法: - - 1. 使用该命令回复要保存的文件, 可选文件名参数. - 示例: - /save custom_file_name.mp4 - - 2. 设置默认存储后, 发送 /save <频道ID/用户名> <消息ID范围> 来批量保存文件. 遵从存储规则, 若未匹配到任何规则则使用默认存储. - 示例: - /save @acherkrau 114-514 - ` -) diff --git a/client/bot/handlers/utils/msgelem/watch.go b/client/bot/handlers/utils/msgelem/watch.go deleted file mode 100644 index bfc9a37..0000000 --- a/client/bot/handlers/utils/msgelem/watch.go +++ /dev/null @@ -1,19 +0,0 @@ -package msgelem - -const ( - WatchHelpText = ` -使用 /watch 命令监听一个聊天的消息, 并自动保存到默认存储中, 遵从存储规则. - -命令语法: -/watch [filter] - -参数: -- : 聊天的 ID 或用户名 -- [filter]: 可选, 格式为 过滤器类型:表达式 , 所有支持类型的过滤器请查看文档 - -命令示例: -/watch 2229835658 msgre:.*plana.* - -这将监听 ID 为 2229835658 的聊天, 并转存所有包含 "plana" 的媒体消息 - ` -) diff --git a/client/bot/handlers/watch.go b/client/bot/handlers/watch.go index ae4b62c..172aa5f 100644 --- a/client/bot/handlers/watch.go +++ b/client/bot/handlers/watch.go @@ -7,7 +7,8 @@ import ( "github.com/celestix/gotgproto/dispatcher" "github.com/celestix/gotgproto/ext" "github.com/charmbracelet/log" - "github.com/krau/SaveAny-Bot/client/bot/handlers/utils/msgelem" + "github.com/krau/SaveAny-Bot/common/i18n" + "github.com/krau/SaveAny-Bot/common/i18n/i18nk" "github.com/krau/SaveAny-Bot/common/utils/tgutil" "github.com/krau/SaveAny-Bot/database" ) @@ -16,7 +17,7 @@ func handleWatchCmd(ctx *ext.Context, update *ext.Update) error { logger := log.FromContext(ctx) args := strings.Split(update.EffectiveMessage.Text, " ") if len(args) < 2 { - ctx.Reply(update, ext.ReplyTextString(msgelem.WatchHelpText), nil) + ctx.Reply(update, ext.ReplyTextString(i18n.T(i18nk.BotMsgWatchHelpText)), nil) return dispatcher.EndGroups } userChatID := update.GetUserChat().GetID() diff --git a/cmd/geni18n/main.go b/cmd/geni18n/main.go index 1c12130..a5845cc 100644 --- a/cmd/geni18n/main.go +++ b/cmd/geni18n/main.go @@ -8,9 +8,10 @@ import ( "io/fs" "os" "path/filepath" - "regexp" "sort" "strings" + + "github.com/goccy/go-yaml" ) func main() { @@ -20,28 +21,27 @@ func main() { flag.Parse() keys := make(map[string]struct{}) - re := regexp.MustCompile(`^\s*\[+\s*([^\]\[]+)\s*\]+`) err := filepath.WalkDir(*dir, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } - if d.IsDir() || !strings.HasSuffix(d.Name(), ".toml") { + if d.IsDir() || !(strings.HasSuffix(d.Name(), ".yaml") || strings.HasSuffix(d.Name(), ".yml")) { return nil } - f, err := os.Open(path) + + data, err := os.ReadFile(path) if err != nil { return err } - defer f.Close() - s := bufio.NewScanner(f) - for s.Scan() { - if m := re.FindStringSubmatch(s.Text()); m != nil { - keys[m[1]] = struct{}{} - } + var content map[string]interface{} + if err := yaml.Unmarshal(data, &content); err != nil { + return fmt.Errorf("failed to parse yaml %s: %w", path, err) } - return s.Err() + + collectKeys(content, "", keys) + return nil }) if err != nil { fmt.Fprintf(os.Stderr, "Error walking directory: %v\n", err) @@ -62,23 +62,44 @@ func main() { defer f.Close() w := bufio.NewWriter(f) - fmt.Fprintf(w, "// Code generated by cmd/gen_i18n. DO NOT EDIT.\n") + fmt.Fprintf(w, "// Code generated by cmd/geni18n. DO NOT EDIT.\n") fmt.Fprintf(w, "package %s\n\n", *pkg) + fmt.Fprintf(w, "type Key string\n\n") fmt.Fprintf(w, "const (\n") for _, key := range list { name := toPascal(key) - fmt.Fprintf(w, "\t%s = %q\n", name, key) + fmt.Fprintf(w, "\t%s Key = %q\n", name, key) } fmt.Fprintf(w, ")\n") w.Flush() } +func collectKeys(node map[string]interface{}, prefix string, keys map[string]struct{}) { + for k, v := range node { + fullKey := k + if prefix != "" { + fullKey = prefix + "." + k + } + switch val := v.(type) { + case map[string]interface{}: + collectKeys(val, fullKey, keys) + default: + keys[fullKey] = struct{}{} + } + } +} + +// 转 PascalCase func toPascal(key string) string { parts := strings.Split(key, ".") for i, p := range parts { - if len(p) > 0 { - parts[i] = strings.ToUpper(string(p[0])) + p[1:] + subs := strings.Split(p, "_") + for j, s := range subs { + if len(s) > 0 { + subs[j] = strings.ToUpper(s[:1]) + s[1:] + } } + parts[i] = strings.Join(subs, "") } return strings.Join(parts, "") } diff --git a/cmd/run.go b/cmd/run.go index db56eb8..25dc12e 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -36,7 +36,7 @@ func Run(cmd *cobra.Command, _ []string) { exitChan, err := initAll(ctx) if err != nil { - logger.Fatal("Failed to initialize", "error", err) + logger.Fatal(i18n.T(i18nk.LifetimeInitfailed), "error", err) } go func() { <-exitChan @@ -46,35 +46,36 @@ func Run(cmd *cobra.Command, _ []string) { core.Run(ctx) <-ctx.Done() - logger.Info(i18n.T(i18nk.Exiting)) - defer logger.Info(i18n.T(i18nk.Bye)) + logger.Info(i18n.T(i18nk.LifetimeExiting)) + defer logger.Info(i18n.T(i18nk.LifetimeBye)) cleanCache() } func initAll(ctx context.Context) (<-chan struct{}, error) { if err := config.Init(ctx); err != nil { - fmt.Println("Failed to load config:", err) - return nil, err + return nil, fmt.Errorf("failed to load config: %w", err) } cache.Init() logger := log.FromContext(ctx) i18n.Init(config.C().Lang) - logger.Info(i18n.T(i18nk.Initing)) + logger.Info(i18n.T(i18nk.LifetimeIniting)) database.Init(ctx) storage.LoadStorages(ctx) if config.C().Parser.PluginEnable { for _, dir := range config.C().Parser.PluginDirs { if err := parsers.LoadPlugins(ctx, dir); err != nil { - logger.Error("Failed to load parser plugins", "dir", dir, "error", err) + logger.Error(i18n.T(i18nk.ParserPluginLoadFailed), "dir", dir, "error", err) } else { - logger.Debug("Loaded parser plugins", "dir", dir) + logger.Debug(i18n.T(i18nk.ParserPluginLoadedDir), "dir", dir) } } } if config.C().Telegram.Userbot.Enable { _, err := userclient.Login(ctx) if err != nil { - logger.Fatalf("User client login failed: %s", err) + logger.Fatal(i18n.T(i18nk.LifetimeUserLoginFailed, map[string]any{ + "Error": err, + })) } } return bot.Init(ctx), nil @@ -86,14 +87,14 @@ func cleanCache() { } if config.C().Temp.BasePath != "" && !config.C().Stream { if slices.Contains([]string{"/", ".", "\\", ".."}, filepath.Clean(config.C().Temp.BasePath)) { - log.Error(i18n.T(i18nk.InvalidCacheDir, map[string]any{ + log.Error(i18n.T(i18nk.ConfigErrInvalidCacheDir, map[string]any{ "Path": config.C().Temp.BasePath, })) return } currentDir, err := os.Getwd() if err != nil { - log.Error(i18n.T(i18nk.GetWorkdirFailed, map[string]any{ + log.Error(i18n.T(i18nk.ErrGetWorkdirFailed, map[string]any{ "Error": err, })) return @@ -101,16 +102,16 @@ func cleanCache() { cachePath := filepath.Join(currentDir, config.C().Temp.BasePath) cachePath, err = filepath.Abs(cachePath) if err != nil { - log.Error(i18n.T(i18nk.GetCacheAbsPathFailed, map[string]any{ + log.Error(i18n.T(i18nk.ErrGetCacheAbsPathFailed, map[string]any{ "Error": err, })) return } - log.Info(i18n.T(i18nk.CleaningCache, map[string]any{ + log.Info(i18n.T(i18nk.LifetimeCleaningCache, map[string]any{ "Path": cachePath, })) if err := fsutil.RemoveAllInDir(cachePath); err != nil { - log.Error(i18n.T(i18nk.CleanCacheFailed, map[string]any{ + log.Error(i18n.T(i18nk.ErrCleanCacheFailed, map[string]any{ "Error": err, })) } diff --git a/cmd/version.go b/cmd/version.go index 6cc8d66..41d939c 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -28,7 +28,7 @@ var upgradeCmd = &cobra.Command{ v := semver.MustParse(config.Version) latest, err := selfupdate.UpdateSelf(v, config.GitRepo) if err != nil { - fmt.Println("Binary update failed:", err) + fmt.Println("Update failed:", err) return } if latest.Version.Equals(v) { diff --git a/common/i18n/i18n.go b/common/i18n/i18n.go index 70e3236..fe587c0 100644 --- a/common/i18n/i18n.go +++ b/common/i18n/i18n.go @@ -5,12 +5,13 @@ import ( "maps" + "github.com/goccy/go-yaml" + "github.com/krau/SaveAny-Bot/common/i18n/i18nk" "github.com/nicksnyder/go-i18n/v2/i18n" - "github.com/pelletier/go-toml/v2" "golang.org/x/text/language" ) -//go:embed locale/*.toml +//go:embed locale/* var localesFS embed.FS var ( @@ -20,7 +21,7 @@ var ( func Init(lang string) { bundle = i18n.NewBundle(language.SimplifiedChinese) - bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) files, err := localesFS.ReadDir("locale") if err != nil { panic("failed to read locale directory: " + err.Error()) @@ -39,7 +40,7 @@ func Init(lang string) { } } -func T(key string, templateData ...map[string]any) string { +func T(key i18nk.Key, templateData ...map[string]any) string { if localizer == nil || bundle == nil { panic("localizer or bundle is not initialized, call Init() first") } @@ -48,11 +49,11 @@ func T(key string, templateData ...map[string]any) string { maps.Copy(templateDataMap, data) } msg, err := localizer.Localize(&i18n.LocalizeConfig{ - MessageID: key, + MessageID: string(key), TemplateData: templateDataMap, }) if err != nil { - return key + return string(key) } return msg } @@ -77,32 +78,32 @@ func TWithLang(lang, key string, templateData ...map[string]any) string { } // Only use in tests or packages that load before i18n -func TWithoutInit(lang, key string, templateData ...map[string]any) string { +func TWithoutInit(lang string, key i18nk.Key, templateData ...map[string]any) string { bundle := i18n.NewBundle(language.SimplifiedChinese) - bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) + bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) files, err := localesFS.ReadDir("locale") if err != nil { - return key + return string(key) } for _, file := range files { if _, err := bundle.LoadMessageFileFS(localesFS, "locale/"+file.Name()); err != nil { - return key + return string(key) } } localizer := i18n.NewLocalizer(bundle, lang) if localizer == nil { - return key + return string(key) } templateDataMap := make(map[string]any) for _, data := range templateData { maps.Copy(templateDataMap, data) } msg, err := localizer.Localize(&i18n.LocalizeConfig{ - MessageID: key, + MessageID: string(key), TemplateData: templateDataMap, }) if err != nil { - return key + return string(key) } return msg } diff --git a/common/i18n/i18nk/keys.go b/common/i18n/i18nk/keys.go index 4635b3c..ccaf38d 100644 --- a/common/i18n/i18nk/keys.go +++ b/common/i18n/i18nk/keys.go @@ -1,19 +1,24 @@ -// Code generated by cmd/gen_i18n. DO NOT EDIT. +// Code generated by cmd/geni18n. DO NOT EDIT. package i18nk +type Key string + const ( - CleanCacheFailed = "CleanCacheFailed" - CleaningCache = "CleaningCache" - ConfigInvalidDuplicateStorageName = "ConfigInvalid.DuplicateStorageName" - ConfigInvalidWorkersOrRetry = "ConfigInvalid.WorkersOrRetry" - CreateRmTimerFailed = "CreateRmTimerFailed" - GetCacheAbsPathFailed = "GetCacheAbsPathFailed" - GetWorkdirFailed = "GetWorkdirFailed" - InvalidCacheDir = "InvalidCacheDir" - LoadedStorages = "LoadedStorages" - RemoveFileAfter = "RemoveFileAfter" - RemoveFileFailed = "RemoveFileFailed" - Bye = "bye" - Exiting = "exiting" - Initing = "initing" + BotMsgHelpTextFmt Key = "bot.msg.help_text_fmt" + BotMsgSaveHelpText Key = "bot.msg.save_help_text" + BotMsgWatchHelpText Key = "bot.msg.watch_help_text" + ConfigErrDuplicateStorageName Key = "config.err.duplicate_storage_name" + ConfigErrInvalidCacheDir Key = "config.err.invalid_cache_dir" + ConfigLoadedStorages Key = "config.loaded_storages" + ErrCleanCacheFailed Key = "err.clean_cache_failed" + ErrGetCacheAbsPathFailed Key = "err.get_cache_abs_path_failed" + ErrGetWorkdirFailed Key = "err.get_workdir_failed" + LifetimeBye Key = "lifetime.bye" + LifetimeCleaningCache Key = "lifetime.cleaning_cache" + LifetimeExiting Key = "lifetime.exiting" + LifetimeInitfailed Key = "lifetime.initfailed" + LifetimeIniting Key = "lifetime.initing" + LifetimeUserLoginFailed Key = "lifetime.user_login_failed" + ParserPluginLoadFailed Key = "parser.plugin.load_failed" + ParserPluginLoadedDir Key = "parser.plugin.loaded_dir" ) diff --git a/common/i18n/locale/zh-Hans.toml b/common/i18n/locale/zh-Hans.toml deleted file mode 100644 index 33c4bb7..0000000 --- a/common/i18n/locale/zh-Hans.toml +++ /dev/null @@ -1,28 +0,0 @@ -[initing] -other = "正在启动..." -[exiting] -other = "正在退出..." -[bye] -other = "已退出" -[InvalidCacheDir] -other = "无效的缓存文件夹: {{.Path}}" -[GetWorkdirFailed] -other = "获取工作目录失败: {{.Error}}" -[GetCacheAbsPathFailed] -other = "获取缓存绝对路径失败: {{.Error}}" -[CleaningCache] -other = "正在清理缓存文件夹: {{.Path}}" -[CleanCacheFailed] -other = "清理缓存失败: {{.Error}}" -[CreateRmTimerFailed] -other = "创建清理定时器失败, 路径: {{.Path}}, 错误: {{.Error}}" -[RemoveFileAfter] -other = "将在 {{.Duration}} 后删除文件: {{.Path}}" -[RemoveFileFailed] -other = "删除文件失败: {{.Path}}, 错误: {{.Error}}" -[LoadedStorages] -other = "已加载 {{.Count}} 个存储" -[ConfigInvalid.WorkersOrRetry] -other = "配置无效: workers 或 retry 必须大于 0, 但当前值为: workers={{.Workers}}, retry={{.Retry}}" -[ConfigInvalid.DuplicateStorageName] -other = "存储名称重复: {{.Name}}" diff --git a/common/i18n/locale/zh-Hans.yaml b/common/i18n/locale/zh-Hans.yaml new file mode 100644 index 0000000..20fd9e8 --- /dev/null +++ b/common/i18n/locale/zh-Hans.yaml @@ -0,0 +1,62 @@ +lifetime: + initing: 正在启动 + initfailed: 初始化失败 + exiting: 正在退出 + user_login_failed: "用户登录失败: {{.Error}}" + cleaning_cache: "正在清理缓存 {{.Path}}" + bye: 已退出 +config: + loaded_storages: "已加载 {{.Count}} 个存储后端" + err: + invalid_cache_dir: "无效的缓存目录: {{.Path}},请检查配置文件" + duplicate_storage_name: "存储名称 '{{.Name}}' 重复,请检查配置文件" +err: + get_workdir_failed: "获取工作目录失败: {{.Error}}" + get_cache_abs_path_failed: "获取缓存绝对路径失败: {{.Error}}" + clean_cache_failed: "清理缓存失败: {{.Error}}" +parser: + plugin: + load_failed: 加载解析器插件失败 + loaded_dir: 解析器插件已加载 +bot: + msg: + help_text_fmt: | + Save Any Bot - 转存你的 Telegram 文件 + 版本: %s , 提交: %s + + 命令: + /start - 开始使用 + /help - 显示帮助 + /silent - 开关静默模式 + /storage - 设置默认存储位置 + /save [自定义文件名] - 保存文件 + /dir - 管理存储目录 + /rule - 管理规则 + /update - 检查更新并升级 + + 使用帮助: https://sabot.unv.app/usage + 反馈群组: https://t.me/ProjectSaveAny + save_help_text: | + 使用方法: + + 1. 使用该命令回复要保存的文件, 可选文件名参数. + 示例: + /save custom_file_name.mp4 + + 2. 设置默认存储后, 发送 /save <频道ID/用户名> <消息ID范围> 来批量保存文件. 遵从存储规则, 若未匹配到任何规则则使用默认存储. + 示例: + /save @acherkrau 114-514 + watch_help_text: | + 使用 /watch 命令监听一个聊天的消息, 并自动保存到默认存储中, 遵从存储规则. + + 命令语法: + /watch [filter] + + 参数: + - : 聊天的 ID 或用户名 + - [filter]: 可选, 格式为 过滤器类型:表达式 , 所有支持类型的过滤器请查看文档 + + 命令示例: + /watch 2229835658 msgre:.*plana.* + + 这将监听 ID 为 2229835658 的聊天, 并转存所有包含 "plana" 的媒体消息 diff --git a/config/viper.go b/config/viper.go index 09c10e9..1ed7e8b 100644 --- a/config/viper.go +++ b/config/viper.go @@ -112,25 +112,28 @@ func Init(ctx context.Context) error { storageNames := make(map[string]struct{}) for _, storage := range cfg.Storages { if _, ok := storageNames[storage.GetName()]; ok { - return errors.New(i18n.TWithoutInit(cfg.Lang, i18nk.ConfigInvalidDuplicateStorageName, map[string]any{ + return errors.New(i18n.TWithoutInit(cfg.Lang, i18nk.ConfigErrDuplicateStorageName, map[string]any{ "Name": storage.GetName(), })) } storageNames[storage.GetName()] = struct{}{} } - fmt.Println(i18n.TWithoutInit(cfg.Lang, i18nk.LoadedStorages, map[string]any{ + fmt.Println(i18n.TWithoutInit(cfg.Lang, i18nk.ConfigLoadedStorages, map[string]any{ "Count": len(cfg.Storages), })) for _, storage := range cfg.Storages { fmt.Printf(" - %s (%s)\n", storage.GetName(), storage.GetType()) } - if cfg.Workers < 1 || cfg.Retry < 1 { - return errors.New(i18n.TWithoutInit(cfg.Lang, i18nk.ConfigInvalidWorkersOrRetry, map[string]any{ - "Workers": cfg.Workers, - "Retry": cfg.Retry, - })) + if cfg.Workers < 1 { + cfg.Workers = 1 + } + if cfg.Threads < 1 { + cfg.Threads = 1 + } + if cfg.Retry < 1 { + cfg.Retry = 1 } for _, storage := range cfg.Storages { diff --git a/go.mod b/go.mod index b00bb06..a44d2e2 100644 --- a/go.mod +++ b/go.mod @@ -50,6 +50,7 @@ require ( github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.18.0 // indirect github.com/google/go-github/v30 v30.1.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e // indirect diff --git a/go.sum b/go.sum index 0c098e7..2d888b3 100644 --- a/go.sum +++ b/go.sum @@ -109,6 +109,8 @@ github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9L github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= diff --git a/main.go b/main.go index 79e9a42..8a52a00 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,8 @@ import ( "github.com/krau/SaveAny-Bot/cmd" ) +//go:generate go run cmd/geni18n/main.go -dir ./common/i18n/locale -out common/i18n/i18nk/keys.go -pkg i18nk + func main() { ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt) defer cancel()