diff --git a/internal/bots/discord/listeners/listeners.go b/internal/bots/discord/listeners/listeners.go index 3ead0ec..bd7e8a2 100644 --- a/internal/bots/discord/listeners/listeners.go +++ b/internal/bots/discord/listeners/listeners.go @@ -58,7 +58,8 @@ func (b *Listeners) smrCmd(event *events.ApplicationCommandInteractionCreate, da if smr.IsUrlCheckError(err) { err = event.CreateMessage( discord.NewMessageCreateBuilder(). - SetContent(smr.FormatUrlCheckError(err, bot.FromPlatformDiscord)). + // TODO: i18n support for discord + SetContent(smr.FormatUrlCheckError(err, bot.FromPlatformDiscord, "", nil)). Build(), ) if err != nil { @@ -89,6 +90,8 @@ func (b *Listeners) smrCmd(event *events.ApplicationCommandInteractionCreate, da Platform: bot.FromPlatformDiscord, URL: urlString, ChannelID: event.Channel().ID().String(), + // TODO: support i18n for discord and slack + Language: "zh-CN", }) if err != nil { b.logger.Warn("discord: failed to add task", zap.Error(err)) diff --git a/internal/bots/slack/handlers/handlers.go b/internal/bots/slack/handlers/handlers.go index 9dc4021..aeeef37 100644 --- a/internal/bots/slack/handlers/handlers.go +++ b/internal/bots/slack/handlers/handlers.go @@ -93,7 +93,8 @@ func (h *Handlers) PostCommandInfo(ctx *gin.Context) { err, originErr := smr.CheckUrl(urlString) if err != nil { if smr.IsUrlCheckError(err) { - ctx.JSON(http.StatusOK, slackbot.NewSlackWebhookMessage(smr.FormatUrlCheckError(err, bot.FromPlatformSlack))) + // TODO: i18n support for slack + ctx.JSON(http.StatusOK, slackbot.NewSlackWebhookMessage(smr.FormatUrlCheckError(err, bot.FromPlatformSlack, "", nil))) return } @@ -125,6 +126,8 @@ func (h *Handlers) PostCommandInfo(ctx *gin.Context) { URL: urlString, ChannelID: body.ChannelID, TeamID: body.TeamID, + // TODO: support i18n for discord and slack + Language: "zh-CN", }) if err != nil { h.logger.Warn("slack: failed to add task", zap.Error(err)) diff --git a/internal/bots/telegram/handlers/recap/recap.go b/internal/bots/telegram/handlers/recap/recap.go index 8fac2c2..8cb646d 100644 --- a/internal/bots/telegram/handlers/recap/recap.go +++ b/internal/bots/telegram/handlers/recap/recap.go @@ -38,7 +38,9 @@ func NewHandlers() func(NewHandlersParams) *Handlers { } func (h *Handlers) Install(dispatcher *tgbot.Dispatcher) { - dispatcher.OnCommandGroup("聊天回顾", []tgbot.Command{ + dispatcher.OnCommandGroup(func(c *tgbot.Context) string { + return "聊天回顾" + }, []tgbot.Command{ { Command: "recap", Handler: tgbot.NewHandler(h.command.handleRecapCommand), diff --git a/internal/bots/telegram/handlers/summarize/smr_command.go b/internal/bots/telegram/handlers/summarize/smr_command.go index cbba333..02cbf53 100644 --- a/internal/bots/telegram/handlers/summarize/smr_command.go +++ b/internal/bots/telegram/handlers/summarize/smr_command.go @@ -1,17 +1,18 @@ package summarize import ( - "fmt" "strings" "time" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "github.com/samber/lo" + "go.uber.org/zap" + "github.com/nekomeowww/insights-bot/internal/services/smr" "github.com/nekomeowww/insights-bot/pkg/bots/tgbot" + "github.com/nekomeowww/insights-bot/pkg/i18n" "github.com/nekomeowww/insights-bot/pkg/types/bot" types "github.com/nekomeowww/insights-bot/pkg/types/smr" - "github.com/samber/lo" - "go.uber.org/zap" ) func (h *Handlers) Handle(c *tgbot.Context) (tgbot.Response, error) { @@ -29,7 +30,7 @@ func (h *Handlers) Handle(c *tgbot.Context) (tgbot.Response, error) { if err != nil { if smr.IsUrlCheckError(err) { return nil, tgbot. - NewMessageError(smr.FormatUrlCheckError(err, bot.FromPlatformTelegram)). + NewMessageError(smr.FormatUrlCheckError(err, bot.FromPlatformTelegram, c.Language(), h.i18n)). WithReply(c.Update.Message). WithParseModeHTML() } @@ -37,7 +38,7 @@ func (h *Handlers) Handle(c *tgbot.Context) (tgbot.Response, error) { return nil, tgbot.NewExceptionError(originErr).WithReply(c.Update.Message) } - message := tgbotapi.NewMessage(c.Update.Message.Chat.ID, "请稍等,量子速读中...") + message := tgbotapi.NewMessage(c.Update.Message.Chat.ID, c.T("commands.groups.summarization.commands.smr.reading")) message.ReplyToMessageID = c.Update.Message.MessageID processingMessage, err := c.Bot.Send(message) @@ -54,7 +55,10 @@ func (h *Handlers) Handle(c *tgbot.Context) (tgbot.Response, error) { } if !ok { return nil, tgbot. - NewMessageError(fmt.Sprintf("很抱歉,您的操作触发了我们的限制机制,为了保证系统的可用性,本命令每最多 %d 分钟最多使用一次,请您耐心等待 %d 分钟后再试,感谢您的理解和支持。", perSeconds, lo.Ternary(ttl/time.Minute <= 1, 1, ttl/time.Minute))). + NewMessageError(c.T("", i18n.M{ + "Seconds": perSeconds, + "SecondsToBeWaited": lo.Ternary(ttl/time.Minute <= 1, 1, ttl/time.Minute), + })). WithReply(c.Update.Message) } @@ -63,9 +67,13 @@ func (h *Handlers) Handle(c *tgbot.Context) (tgbot.Response, error) { URL: urlString, ChatID: c.Update.Message.Chat.ID, MessageID: processingMessage.MessageID, + Language: c.Language(), }) if err != nil { - return nil, tgbot.NewExceptionError(err).WithMessage("量子速读失败了,可以再试试?").WithEdit(&processingMessage) + return nil, tgbot. + NewExceptionError(err). + WithMessage(c.T("commands.groups.summarization.commands.smr.failedToRead")). + WithEdit(&processingMessage) } return nil, nil diff --git a/internal/bots/telegram/handlers/summarize/summarize.go b/internal/bots/telegram/handlers/summarize/summarize.go index 3c6bfa4..c2c8aff 100644 --- a/internal/bots/telegram/handlers/summarize/summarize.go +++ b/internal/bots/telegram/handlers/summarize/summarize.go @@ -4,6 +4,7 @@ import ( "github.com/nekomeowww/insights-bot/internal/models/smr" "github.com/nekomeowww/insights-bot/internal/services/smr/smrqueue" "github.com/nekomeowww/insights-bot/pkg/bots/tgbot" + "github.com/nekomeowww/insights-bot/pkg/i18n" "github.com/nekomeowww/insights-bot/pkg/logger" "go.uber.org/fx" ) @@ -18,6 +19,7 @@ type NewHandlersParams struct { fx.In Logger *logger.Logger + I18n *i18n.I18n SMR *smr.Model SmrQueue *smrqueue.Queue } @@ -26,6 +28,7 @@ var _ tgbot.HandlerGroup = (*Handlers)(nil) type Handlers struct { logger *logger.Logger + i18n *i18n.I18n smr *smr.Model smrQueue *smrqueue.Queue } @@ -34,6 +37,7 @@ func NewHandlers() func(NewHandlersParams) *Handlers { return func(param NewHandlersParams) *Handlers { handler := &Handlers{ logger: param.Logger, + i18n: param.I18n, smrQueue: param.SmrQueue, smr: param.SMR, } @@ -43,12 +47,14 @@ func NewHandlers() func(NewHandlersParams) *Handlers { } func (h *Handlers) Install(dispatcher *tgbot.Dispatcher) { - dispatcher.OnCommandGroup("量子速读", []tgbot.Command{ + dispatcher.OnCommandGroup(func(c *tgbot.Context) string { + return c.T("commands.groups.summarization.name") + }, []tgbot.Command{ { Command: "smr", Handler: tgbot.NewHandler(h.Handle), HelpMessage: func(c *tgbot.Context) string { - return "量子速读网页文章(也支持在频道中使用) 用法:/smr <链接>" + return c.T("commands.groups.summarization.commands.smr.help") }, }, }) diff --git a/internal/services/smr/processor.go b/internal/services/smr/processor.go index 9b63f53..2d06f5b 100644 --- a/internal/services/smr/processor.go +++ b/internal/services/smr/processor.go @@ -31,14 +31,14 @@ func (s *Service) processOutput(info types.TaskInfo, result *smr.URLSummarizatio } } -func (s *Service) processError(err error) string { +func (s *Service) processError(err error, language string) string { if errors.Is(err, smr.ErrContentNotSupported) { - return "暂时不支持量子速读这样的内容呢,可以换个别的链接试试。" + return s.i18n.TWithLanguage(language, "commands.groups.summarization.commands.smr.contentNotSupported") } else if errors.Is(err, smr.ErrNetworkError) || errors.Is(err, smr.ErrRequestFailed) { - return "量子速读的链接读取失败了哦。可以再试试?" + return s.i18n.TWithLanguage(language, "commands.groups.summarization.commands.smr.failedToReadDueToFailedToFetch") } - return "量子速读失败了。可以再试试?" + return s.i18n.TWithLanguage(language, "commands.groups.summarization.commands.smr.failedToRead") } func (s *Service) sendResult(output *smr.URLSummarizationOutput, info types.TaskInfo, result string) { @@ -174,7 +174,8 @@ func (s *Service) processor(info types.TaskInfo) { smrResult, err := s.model.SummarizeInputURL(ctx, info.URL, info.Platform) if err != nil { s.logger.Warn("smr service: summarization failed", zap.Error(err)) - errStr := s.processError(err) + // TODO: support i18n for discord and slack + errStr := s.processError(err, lo.Ternary(info.Language == "", "en", info.Language)) s.sendResult(nil, info, errStr) return diff --git a/internal/services/smr/smr.go b/internal/services/smr/smr.go index 3dca761..13763de 100644 --- a/internal/services/smr/smr.go +++ b/internal/services/smr/smr.go @@ -13,6 +13,7 @@ import ( "github.com/nekomeowww/insights-bot/pkg/bots/discordbot" "github.com/nekomeowww/insights-bot/pkg/bots/slackbot" "github.com/nekomeowww/insights-bot/pkg/bots/tgbot" + "github.com/nekomeowww/insights-bot/pkg/i18n" "github.com/nekomeowww/insights-bot/pkg/logger" "github.com/redis/rueidis" "github.com/samber/lo" @@ -34,6 +35,7 @@ type NewServiceParam struct { Config *configs.Config Logger *logger.Logger + I18n *i18n.I18n RedisClient *datastore.Redis Ent *datastore.Ent @@ -49,6 +51,7 @@ type NewServiceParam struct { type Service struct { logger *logger.Logger config *configs.Config + i18n *i18n.I18n ent *datastore.Ent @@ -73,8 +76,9 @@ func NewService() func(param NewServiceParam) (*Service, error) { return func(param NewServiceParam) (*Service, error) { s := &Service{ logger: param.Logger, - ent: param.Ent, + i18n: param.I18n, config: param.Config, + ent: param.Ent, model: param.Model, queue: param.Queue, tgBot: param.TgBot, diff --git a/internal/services/smr/utils.go b/internal/services/smr/utils.go index 5d85bdd..2f99736 100644 --- a/internal/services/smr/utils.go +++ b/internal/services/smr/utils.go @@ -4,6 +4,7 @@ import ( "errors" "net/url" + "github.com/nekomeowww/insights-bot/pkg/i18n" "github.com/nekomeowww/insights-bot/pkg/types/bot" "github.com/samber/lo" ) @@ -24,12 +25,13 @@ func CheckUrl(urlString string) (error, error) { return nil, nil } -func FormatUrlCheckError(err error, platform bot.FromPlatform) string { +func FormatUrlCheckError(err error, platform bot.FromPlatform, language string, i18n *i18n.I18n) string { switch { case errors.Is(err, ErrNoLink): switch platform { case bot.FromPlatformTelegram: - return "没有找到链接,可以发送一个有效的链接吗?用法:/smr <链接>" + return i18n.TWithLanguage(language, "commands.groups.summarization.commands.smr.noLinksFound") + // TODO: support i18n for discord and slack case bot.FromPlatformDiscord, bot.FromPlatformSlack: return "没有找到链接,可以发送一个有效的链接吗?用法:`/smr <链接>`" default: @@ -38,8 +40,9 @@ func FormatUrlCheckError(err error, platform bot.FromPlatform) string { case errors.Is(err, ErrParse), errors.Is(err, ErrScheme): switch platform { case bot.FromPlatformTelegram: - return "你发来的链接无法被理解,可以重新发一个试试。用法:/smr <链接>" + return i18n.TWithLanguage(language, "commands.groups.summarization.commands.smr.invalidLink") case bot.FromPlatformDiscord, bot.FromPlatformSlack: + // TODO: support i18n for discord and slack return "你发来的链接无法被理解,可以重新发一个试试。用法:`/smr <链接>`" default: return err.Error() diff --git a/locales/en.yaml b/locales/en.yaml index e0c6bc6..f4a8472 100644 --- a/locales/en.yaml +++ b/locales/en.yaml @@ -1,15 +1,54 @@ system: commands: - start: - help: Start interacting with the bot - help: - help: Get help - message: | - Hi! 👋 Welcome using Insights Bot! - - I currently support these commands: - - {{ .Commands }} - cancel: - help: Cancel the current activated operation - alreadyCancelledAll: There is no activated operation to cancel + groups: + basic: + name: 基础命令 + commands: + start: + help: Start interacting with the bot + help: + help: Get help + message: | + Hi! 👋 Welcome using Insights Bot! + + I currently support these commands: + + {{ .Commands }} + cancel: + help: Cancel the current activated operation + alreadyCancelledAll: There is no activated operation to cancel + +commands: + groups: + summarization: + name: 量子速读 + commands: + smr: + help: 量子速读网页文章(也支持在频道中使用) 用法:/smr <链接> + noLinksFound: 没有找到链接,可以发送一个有效的链接吗?用法:/smr <链接> + invalidLink: 你发来的链接无法被理解,可以重新发一个试试。用法:/smr <链接> + reading: 请稍等,量子速读中... + rateLimitExceeded: 很抱歉,您的操作触发了我们的限制机制,为了保证系统的可用性,本命令每最多 {{ .Seconds }} 秒使用一次,请您耐心等待 {{ .SecondsToBeWaited }} 秒后再试,感谢您的理解和支持。 + failedToRead: 量子速读失败了,可以再试试? + failedToReadDueToFailedToFetch: 量子速读的链接读取失败了哦。可以再试试? + contentNotSupported: 暂时不支持量子速读这样的内容呢,可以换个别的链接试试。 + +prompts: + smr: + - role: system + content: | + 你是我的网页文章阅读助理。我将为你提供文章的标题、作 + 者、所抓取的网页中的正文等信息,然后你将对文章做出总结。\n请你在总结时满足以下要求: + 1. 首先如果文章的标题不是中文的请依据上下文将标题信达雅的翻译为简体中文并放在第一行 + 2. 然后从我提供的文章信息中总结出一个三百字以内的文章的摘要 + 3. 最后,你将利用你已有的知识和经验,对我提供的文章信息提出 3 个具有创造性和发散思维的问题 + 4. 请用简体中文进行回复 + 最终你回复的消息格式应像这个例句一样(例句中的双花括号为需要替换的内容):\n + {{简体中文标题,可省略}}\n\n摘要:{{文章的摘要}}\n\n关联提问:\n1. {{关联提问 1}}\n2. {{关联提问 2}}\n2. {{关联提问 3}} + - role: user + content: | + 我的第一个要求相关的信息如下: + 文章标题:{{ .Title }} + 文章作者:{{ .By }} + 文章正文:{{ .Content }} + 接下来请你完成我所要求的任务。 diff --git a/locales/zh-CN.yaml b/locales/zh-CN.yaml index b500dea..6b9ce51 100644 --- a/locales/zh-CN.yaml +++ b/locales/zh-CN.yaml @@ -1,15 +1,54 @@ system: commands: - start: - help: 开始与 Bot 的交互 - help: - help: 获取帮助 - message: | - 你好!👋 欢迎使用 Insights Bot! - - 我当前支持这些命令: - - {{ .Commands }} - cancel: - help: 取消当前操作 - alreadyCancelledAll: 已经没有正在进行的操作了 + groups: + basic: + name: 基础命令 + commands: + start: + help: 开始与 Bot 的交互 + help: + help: 获取帮助 + message: | + 你好!👋 欢迎使用 Insights Bot! + + 我当前支持这些命令: + + {{ .Commands }} + cancel: + help: 取消当前操作 + alreadyCancelledAll: 已经没有正在进行的操作了 + +commands: + groups: + summarization: + name: 量子速读 + commands: + smr: + help: 量子速读网页文章(也支持在频道中使用) 用法:/smr <链接> + noLinksFound: 没有找到链接,可以发送一个有效的链接吗?用法:/smr <链接> + invalidLink: 你发来的链接无法被理解,可以重新发一个试试。用法:/smr <链接> + reading: 请稍等,量子速读中... + rateLimitExceeded: 很抱歉,您的操作触发了我们的限制机制,为了保证系统的可用性,本命令每最多 {{ .Seconds }} 秒使用一次,请您耐心等待 {{ .SecondsToBeWaited }} 秒后再试,感谢您的理解和支持。 + failedToRead: 量子速读失败了,可以再试试? + failedToReadDueToFailedToFetch: 量子速读的链接读取失败了哦。可以再试试? + contentNotSupported: 暂时不支持量子速读这样的内容呢,可以换个别的链接试试。 + +prompts: + smr: + - role: system + content: | + 你是我的网页文章阅读助理。我将为你提供文章的标题、作 + 者、所抓取的网页中的正文等信息,然后你将对文章做出总结。\n请你在总结时满足以下要求: + 1. 首先如果文章的标题不是中文的请依据上下文将标题信达雅的翻译为简体中文并放在第一行 + 2. 然后从我提供的文章信息中总结出一个三百字以内的文章的摘要 + 3. 最后,你将利用你已有的知识和经验,对我提供的文章信息提出 3 个具有创造性和发散思维的问题 + 4. 请用简体中文进行回复 + 最终你回复的消息格式应像这个例句一样(例句中的双花括号为需要替换的内容):\n + {{简体中文标题,可省略}}\n\n摘要:{{文章的摘要}}\n\n关联提问:\n1. {{关联提问 1}}\n2. {{关联提问 2}}\n2. {{关联提问 3}} + - role: user + content: | + 我的第一个要求相关的信息如下: + 文章标题:{{ .Title }} + 文章作者:{{ .By }} + 文章正文:{{ .Content }} + 接下来请你完成我所要求的任务。 diff --git a/pkg/bots/tgbot/cancel_command.go b/pkg/bots/tgbot/cancel_command.go index e850fc8..9e89300 100644 --- a/pkg/bots/tgbot/cancel_command.go +++ b/pkg/bots/tgbot/cancel_command.go @@ -30,7 +30,7 @@ func (h *cancelCommandHandler) Command() string { } func (h *cancelCommandHandler) CommandHelp(c *Context) string { - return c.T("system.commands.cancel.help") + return c.T("system.commands.groups.basic.commands.cancel.help") } func (h *cancelCommandHandler) handle(c *Context) (Response, error) { @@ -59,5 +59,5 @@ func (h *cancelCommandHandler) handle(c *Context) (Response, error) { return nil, err } - return c.NewMessageReplyTo(c.T("system.commands.cancel.alreadyCancelledAll"), c.Update.Message.MessageID), nil + return c.NewMessageReplyTo(c.T("system.commands.groups.basic.commands.cancel.alreadyCancelledAll"), c.Update.Message.MessageID), nil } diff --git a/pkg/bots/tgbot/dispatcher.go b/pkg/bots/tgbot/dispatcher.go index d9c3bb3..f3c2d18 100644 --- a/pkg/bots/tgbot/dispatcher.go +++ b/pkg/bots/tgbot/dispatcher.go @@ -56,7 +56,9 @@ func NewDispatcher() func(logger *logger.Logger, i18n *i18n.I18n) *Dispatcher { d.startCommandHandler.helpCommandHandler = d.helpCommand - d.OnCommandGroup("基础命令", []Command{ + d.OnCommandGroup(func(c *Context) string { + return c.T("system.commands.groups.basic.name") + }, []Command{ {Command: d.helpCommand.Command(), HelpMessage: d.helpCommand.CommandHelp, Handler: NewHandler(d.helpCommand.handle)}, {Command: d.cancelCommand.Command(), HelpMessage: d.cancelCommand.CommandHelp, Handler: NewHandler(d.cancelCommand.handle)}, {Command: d.startCommandHandler.Command(), HelpMessage: d.startCommandHandler.CommandHelp, Handler: NewHandler(d.startCommandHandler.handle)}, @@ -82,7 +84,7 @@ func (d *Dispatcher) OnCommand(cmd string, commandHelp func(c *Context) string, d.commandHandlers[cmd] = h.Handle } -func (d *Dispatcher) OnCommandGroup(groupName string, group []Command) { +func (d *Dispatcher) OnCommandGroup(groupName func(*Context) string, group []Command) { d.helpCommand.commandGroups = append(d.helpCommand.commandGroups, commandGroup{name: groupName, commands: group}) for _, c := range group { diff --git a/pkg/bots/tgbot/help_command.go b/pkg/bots/tgbot/help_command.go index 1ea7aca..3cb58d2 100644 --- a/pkg/bots/tgbot/help_command.go +++ b/pkg/bots/tgbot/help_command.go @@ -16,7 +16,7 @@ type Command struct { } type commandGroup struct { - name string + name func(*Context) string commands []Command } @@ -38,7 +38,7 @@ func (h *helpCommandHandler) Command() string { } func (h *helpCommandHandler) CommandHelp(c *Context) string { - return c.T("system.commands.help.help") + return c.T("system.commands.groups.basic.commands.help.help") } func (h *helpCommandHandler) handle(c *Context) (Response, error) { @@ -85,8 +85,18 @@ func (h *helpCommandHandler) handle(c *Context) (Response, error) { commandHelpMessages = append(commandHelpMessages, commandHelpMessage.String()) } - commandGroupHelpMessages = append(commandGroupHelpMessages, fmt.Sprintf("%s%s", lo.Ternary(group.name != "", fmt.Sprintf("%s\n\n", EscapeHTMLSymbols(group.name)), ""), strings.Join(commandHelpMessages, "\n"))) + commandGroupHelpMessages = append(commandGroupHelpMessages, fmt.Sprintf("%s%s", lo.Ternary( + group.name(c) != "", + fmt.Sprintf("%s\n\n", EscapeHTMLSymbols(group.name(c))), ""), + strings.Join(commandHelpMessages, "\n"), + )) } - return c.NewMessageReplyTo(c.T("system.commands.help.message", i18n.M{"Commands": strings.Join(commandGroupHelpMessages, "\n\n")}), c.Update.Message.MessageID).WithParseModeHTML(), nil + return c. + NewMessageReplyTo( + c.T("system.commands.groups.basic.commands.help.message", i18n.M{ + "Commands": strings.Join(commandGroupHelpMessages, "\n\n"), + }), + c.Update.Message.MessageID). + WithParseModeHTML(), nil } diff --git a/pkg/bots/tgbot/start_command.go b/pkg/bots/tgbot/start_command.go index 2cf8924..f0e92e9 100644 --- a/pkg/bots/tgbot/start_command.go +++ b/pkg/bots/tgbot/start_command.go @@ -25,7 +25,7 @@ func (h *startCommandHandler) Command() string { } func (h *startCommandHandler) CommandHelp(c *Context) string { - return c.T("system.commands.start.help") + return c.T("system.commands.groups.basic.commands.start.help") } func (h *startCommandHandler) handle(c *Context) (Response, error) { diff --git a/pkg/types/smr/task.go b/pkg/types/smr/task.go index a7a40e2..f3b536d 100644 --- a/pkg/types/smr/task.go +++ b/pkg/types/smr/task.go @@ -9,6 +9,7 @@ import ( type TaskInfo struct { Platform bot.FromPlatform `json:"platform"` URL string `json:"url"` // url to summarize + Language string `json:"language"` ChatID int64 `json:"chatID"` // only for telegram MessageID int `json:"messageID"` // used to edit the reply message of request, not work in slack or discordbot currently