Refactor error handling in services to use internationalized error messages

- Updated OSSStorage validation errors to use internationalized messages.
- Changed error messages in provider.go for unsupported file storage types.
- Refactored tag_service.go to replace hardcoded error messages with internationalized versions.
- Updated ticket_service.go to use internationalized error messages for various validation checks.
- Refactored ticket_tag_service.go to use internationalized error messages for tag validation.
- Changed ticket_view_service.go to use internationalized error messages for view validation.
- Updated tool_catalog_service.go to use internationalized error messages for tool code validation.
- Refactored user_service.go to replace error messages with internationalized versions.
- Updated ws_service.go to use internationalized error messages for WebSocket handling.
- Refactored wxwork_kf_inbound_service.go to use internationalized error messages for message handling.
- Updated wxwork_kf_outbound_service.go to use internationalized error messages for outbound message handling.
- Refactored wxwork_login_service.go to use internationalized error messages for login handling.
- Updated login.go in wxwork package to use internationalized error messages for login state and ticket validation.
This commit is contained in:
mlogclub
2026-06-02 20:51:13 +08:00
parent 77be1b4f5e
commit 0b4a1b4594
103 changed files with 1688 additions and 1350 deletions
@@ -76,4 +76,5 @@ type AgentTeamScheduleBatchRequest struct {
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
Remark string `json:"remark"`
Locale string `json:"-"`
}
@@ -101,6 +101,7 @@ type ImportKnowledgeFAQRequest struct {
Mode KnowledgeFAQImportMode
Filename string
Reader io.Reader
Locale string
}
type KnowledgeSearchRequest struct {
+71 -1
View File
@@ -1,6 +1,10 @@
package errorsx
import "github.com/mlogclub/simple/web"
import (
"agent-desk/internal/pkg/i18nx"
"github.com/mlogclub/simple/web"
)
const (
CodeInvalidParam = 1000
@@ -16,26 +20,92 @@ func InvalidParam(message string) error {
return web.NewError(CodeInvalidParam, message)
}
func InvalidParamI18n(key string, args ...any) error {
return NewI18nError(CodeInvalidParam, key, args...)
}
func BusinessError(code int, message string) error {
return web.NewError(CodeBusinessError+code, message)
}
func BusinessErrorI18n(code int, key string, args ...any) error {
return NewI18nError(CodeBusinessError+code, key, args...)
}
func Unauthorized(message string) error {
return web.NewError(CodeAuthUnauthorized, message)
}
func UnauthorizedI18n(key string, args ...any) error {
return NewI18nError(CodeAuthUnauthorized, key, args...)
}
func Forbidden(message string) error {
return web.NewError(CodeAuthForbidden, message)
}
func ForbiddenI18n(key string, args ...any) error {
return NewI18nError(CodeAuthForbidden, key, args...)
}
func InvalidToken(message string) error {
return web.NewError(CodeAuthInvalidToken, message)
}
func InvalidTokenI18n(key string, args ...any) error {
return NewI18nError(CodeAuthInvalidToken, key, args...)
}
func InvalidAccount(message string) error {
return web.NewError(CodeAuthInvalidAccount, message)
}
func InvalidAccountI18n(key string, args ...any) error {
return NewI18nError(CodeAuthInvalidAccount, key, args...)
}
func CredentialLocked(message string) error {
return web.NewError(CodeAuthCredentialLocked, message)
}
func CredentialLockedI18n(key string, args ...any) error {
return NewI18nError(CodeAuthCredentialLocked, key, args...)
}
type I18nError struct {
Code int
Key string
Args []any
}
func NewI18nError(code int, key string, args ...any) *I18nError {
return &I18nError{Code: code, Key: key, Args: args}
}
func (e *I18nError) Error() string {
if e == nil {
return ""
}
return e.Message(i18nx.LocaleZhCN)
}
func (e *I18nError) Unwrap() error {
if e == nil {
return nil
}
return web.NewError(e.Code, e.Error())
}
func (e *I18nError) Message(locale string) string {
if e == nil {
return ""
}
return i18nx.Getf(locale, e.Key, e.Args...)
}
func (e *I18nError) JsonResult(locale string) *web.JsonResult {
if e == nil {
return web.JsonSuccess()
}
return web.JsonErrorCode(e.Code, e.Message(locale))
}
+12
View File
@@ -0,0 +1,12 @@
package httpx
import (
"agent-desk/internal/pkg/i18nx"
"github.com/gin-gonic/gin"
"github.com/mlogclub/simple/web"
)
func JsonErrorMsg(ctx *gin.Context, key string, args ...any) *web.JsonResult {
return web.JsonErrorMsg(i18nx.T(ctx, key, args...))
}
+1 -1
View File
@@ -12,7 +12,7 @@ import (
func GetPathInt64(ctx *gin.Context, name string) (int64, bool) {
value, err := strconv.ParseInt(ctx.Param(name), 10, 64)
if err != nil {
WriteHttpStatusJSON(ctx, http.StatusBadRequest, web.JsonErrorMsg(i18nx.T(ctx, "error.path.invalid", nil)))
WriteHttpStatusJSON(ctx, http.StatusBadRequest, web.JsonErrorMsg(i18nx.T(ctx, "error.path.invalid")))
return 0, false
}
return value, true
+14 -11
View File
@@ -1,6 +1,7 @@
package httpx
import (
"agent-desk/internal/pkg/errorsx"
"agent-desk/internal/pkg/i18nx"
"net/http"
@@ -20,6 +21,10 @@ type pageData struct {
paging *sqls.Paging
}
type localizedError interface {
Message(locale string) string
}
func CursorData(results any, cursor string, hasMore bool) any {
return cursorData{results: results, cursor: cursor, hasMore: hasMore}
}
@@ -29,22 +34,14 @@ func PageData(results any, paging *sqls.Paging) any {
}
func WriteJSON(ctx *gin.Context, result any) {
ctx.JSON(http.StatusOK, localizeJSONResult(ctx, buildJSONResult(result)))
ctx.JSON(http.StatusOK, buildJSONResult(ctx, result))
}
func WriteHttpStatusJSON(ctx *gin.Context, statusCode int, result any) {
ctx.JSON(statusCode, localizeJSONResult(ctx, buildJSONResult(result)))
ctx.JSON(statusCode, buildJSONResult(ctx, result))
}
func localizeJSONResult(ctx *gin.Context, result *web.JsonResult) *web.JsonResult {
if result == nil || result.Success || result.Message == "" {
return result
}
result.Message = i18nx.TranslateKnownMessage(i18nx.Locale(ctx), result.Message)
return result
}
func buildJSONResult(result any) *web.JsonResult {
func buildJSONResult(ctx *gin.Context, result any) *web.JsonResult {
switch value := result.(type) {
case nil:
return web.JsonSuccess()
@@ -56,6 +53,12 @@ func buildJSONResult(result any) *web.JsonResult {
return web.JsonError(value)
case web.CodeError:
return web.JsonError(&value)
case *errorsx.I18nError:
return value.JsonResult(i18nx.Locale(ctx))
case errorsx.I18nError:
return value.JsonResult(i18nx.Locale(ctx))
case localizedError:
return web.JsonErrorMsg(value.Message(i18nx.Locale(ctx)))
case error:
return web.JsonError(value)
case cursorData:
+3 -2
View File
@@ -1,6 +1,7 @@
package httpx
import (
"agent-desk/internal/pkg/errorsx"
"agent-desk/internal/pkg/i18nx"
"encoding/json"
"errors"
@@ -68,12 +69,12 @@ func TestWriteJSONWrapsCommonResultTypes(t *testing.T) {
}
}
func TestWriteJSONLocalizesKnownErrorMessages(t *testing.T) {
func TestWriteJSONLocalizesI18nErrors(t *testing.T) {
gin.SetMode(gin.TestMode)
ctx, recorder := testContext()
i18nx.SetLocale(ctx, i18nx.LocaleEnUS)
WriteJSON(ctx, web.JsonErrorMsg("会话不存在"))
WriteJSON(ctx, errorsx.InvalidParamI18n("error.e0116"))
var got web.JsonResult
if err := json.Unmarshal(recorder.Body.Bytes(), &got); err != nil {
-36
View File
@@ -1,36 +0,0 @@
package i18nx
import (
"embed"
"log/slog"
"sync"
"github.com/nicksnyder/go-i18n/v2/i18n"
"github.com/pelletier/go-toml/v2"
"golang.org/x/text/language"
)
//go:embed locales/*.toml
var messageFiles embed.FS
var (
bundleOnce sync.Once
bundle *i18n.Bundle
)
func Bundle() *i18n.Bundle {
bundleOnce.Do(func() {
b := i18n.NewBundle(language.AmericanEnglish)
b.RegisterUnmarshalFunc("toml", toml.Unmarshal)
for _, name := range []string{
"locales/active.zh-CN.toml",
"locales/active.en-US.toml",
} {
if _, err := b.LoadMessageFileFS(messageFiles, name); err != nil {
slog.Error("load i18n message file failed", "file", name, "err", err)
}
}
bundle = b
})
return bundle
}
+38
View File
@@ -0,0 +1,38 @@
package i18nx
type Error struct {
Key string
Args []any
Cause error
}
func Errorf(key string, args ...any) *Error {
err := &Error{Key: key, Args: args}
for _, arg := range args {
if cause, ok := arg.(error); ok {
err.Cause = cause
}
}
return err
}
func (e *Error) Error() string {
if e == nil {
return ""
}
return e.Message(LocaleZhCN)
}
func (e *Error) Message(locale string) string {
if e == nil {
return ""
}
return Getf(locale, e.Key, e.Args...)
}
func (e *Error) Unwrap() error {
if e == nil {
return nil
}
return e.Cause
}
+41 -90
View File
@@ -63,111 +63,63 @@ func TestResolveLocalePrefersXLocale(t *testing.T) {
func TestTranslateFallsBackToEnglish(t *testing.T) {
t.Parallel()
if got := TLocale(LocaleEnUS, "error.auth.expired", nil); got != "Your session has expired. Please sign in again." {
if got := TLocale(LocaleEnUS, "error.auth.expired"); got != "Your session has expired. Please sign in again." {
t.Fatalf("english translation = %q", got)
}
if got := TLocale("fr-FR", "error.auth.expired", nil); got != "Your session has expired. Please sign in again." {
if got := TLocale("fr-FR", "error.auth.expired"); got != "Your session has expired. Please sign in again." {
t.Fatalf("fallback translation = %q", got)
}
}
func TestTranslateKnownMessageSupportsFormattedMessages(t *testing.T) {
func TestGetfSupportsLocaleKeys(t *testing.T) {
t.Parallel()
tests := []struct {
name string
message string
want string
name string
locale string
key string
args []any
want string
}{
{
name: "batch limit",
message: "单次最多生成 31 条排班",
want: "You can generate at most 31 schedule entries at a time.",
name: "batch limit",
locale: LocaleEnUS,
key: "error.agentTeamSchedule.batchLimit",
args: []any{31},
want: "You can generate at most 31 schedule entries at a time.",
},
{
name: "start date",
message: "开始时间格式错误,请使用 yyyy-MM-dd",
want: "Invalid start time format. Use yyyy-MM-dd.",
name: "directory not found",
locale: LocaleEnUS,
key: "error.e0273",
want: "Directory not found.",
},
{
name: "end date time",
message: "结束时间格式错误,请使用 yyyy-MM-dd HH:mm:ss 或 RFC3339",
want: "Invalid end time format. Use yyyy-MM-dd HH:mm:ss or RFC3339.",
name: "single knowledge base reference",
locale: LocaleEnUS,
key: "error.knowledgeBase.referencedByAgent",
args: []any{"SalesBot"},
want: "This knowledge base is referenced by AI Agent \"SalesBot\". Remove the binding first.",
},
{
name: "oidc issuer config",
message: "OIDC issuer 未配置",
want: "OIDC issuer is not configured.",
name: "multiple knowledge base references",
locale: LocaleEnUS,
key: "error.knowledgeBase.referencedByAgents",
args: []any{3},
want: "This knowledge base is referenced by 3 AI Agents. Remove the bindings first.",
},
{
name: "oidc login result",
message: "登录结果不能为空",
want: "Sign-in result is required.",
name: "faq import duplicated question",
locale: LocaleEnUS,
key: "error.knowledgeFAQImport.duplicateQuestionInFile",
args: []any{12},
want: "The standard question is duplicated in the same file. It first appeared on row 12.",
},
{
name: "directory not found",
message: "目录不存在",
want: "Directory not found.",
},
{
name: "directory has docs",
message: "该目录下存在文档,无法删除",
want: "This directory contains documents and cannot be deleted.",
},
{
name: "move documents",
message: "请选择要移动的文档",
want: "Select documents to move.",
},
{
name: "move faqs across knowledge bases",
message: "只能移动当前知识库下的FAQ",
want: "Only FAQs in the current knowledge base can be moved.",
},
{
name: "single knowledge base reference",
message: "知识库已被 AI Agent「SalesBot」引用,请先解除绑定",
want: "This knowledge base is referenced by AI Agent \"SalesBot\". Remove the binding first.",
},
{
name: "multiple knowledge base references",
message: "知识库已被 3 个 AI Agent 引用,请先解除绑定",
want: "This knowledge base is referenced by 3 AI Agents. Remove the bindings first.",
},
{
name: "schedule conflict range",
message: "该客服组在 2026-06-02 09:00:00 至 2026-06-02 10:00:00 已存在排班",
want: "This agent team already has a schedule from 2026-06-02 09:00:00 to 2026-06-02 10:00:00.",
},
{
name: "faq import duplicated question",
message: "同一文件中标准问题重复,首次出现于第12行",
want: "The standard question is duplicated in the same file. It first appeared on row 12.",
},
{
name: "faq import existing question",
message: "标准问题已存在,已跳过",
want: "The standard question already exists and was skipped.",
},
{
name: "required param",
message: "参数:name不能为空",
want: "Parameter \"name\" is required.",
},
{
name: "mcp call failed",
message: "调用 MCP 工具失败: timeout",
want: "Failed to call MCP tool: timeout",
},
{
name: "wxwork unsupported outbound",
message: "当前暂不支持企业微信下行消息类型: voice",
want: "The current WeCom outbound message type is not supported yet: voice",
},
{
name: "wxwork callback missing",
message: "企业微信登录回调地址未配置",
want: "WeCom sign-in callback URL is not configured.",
name: "zh keeps chinese value",
locale: LocaleZhCN,
key: "error.e0273",
want: "目录不存在",
},
}
@@ -175,19 +127,18 @@ func TestTranslateKnownMessageSupportsFormattedMessages(t *testing.T) {
tt := tt
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
if got := TranslateKnownMessage(LocaleEnUS, tt.message); got != tt.want {
t.Fatalf("TranslateKnownMessage() = %q, want %q", got, tt.want)
if got := Getf(tt.locale, tt.key, tt.args...); got != tt.want {
t.Fatalf("Getf() = %q, want %q", got, tt.want)
}
})
}
}
func TestTranslateKnownMessageKeepsChineseLocale(t *testing.T) {
func TestGetfFallsBackToKey(t *testing.T) {
t.Parallel()
message := "目录不存在"
if got := TranslateKnownMessage(LocaleZhCN, message); got != message {
t.Fatalf("TranslateKnownMessage() = %q, want %q", got, message)
if got := Getf(LocaleEnUS, "missing.key"); got != "missing.key" {
t.Fatalf("missing translation = %q, want %q", got, "missing.key")
}
}
-440
View File
@@ -1,440 +0,0 @@
package i18nx
import (
"fmt"
"regexp"
"strings"
)
var (
maxScheduleBatchMessagePattern = regexp.MustCompile(`^单次最多生成 ([0-9]+) 条排班$`)
knowledgeBaseReferencedByAgentPattern = regexp.MustCompile(`^知识库已被 AI Agent「(.+)」引用,请先解除绑定$`)
knowledgeBaseReferencedByAgentsPattern = regexp.MustCompile(`^知识库已被 ([0-9]+) 个 AI Agent 引用,请先解除绑定$`)
agentTeamScheduleConflictPattern = regexp.MustCompile(`^该客服组在 (.+) 至 (.+) 已存在排班$`)
knowledgeFAQImportDuplicatePattern = regexp.MustCompile(`^同一文件中标准问题重复,首次出现于第([0-9]+)行$`)
requiredParamMessagePattern = regexp.MustCompile(`^参数:(.+)不能为空$`)
mcpListToolsMessagePattern = regexp.MustCompile(`^列出 MCP 工具失败: (.+)$`)
mcpCallToolMessagePattern = regexp.MustCompile(`^调用 MCP 工具失败: (.+)$`)
mcpConnectServerMessagePattern = regexp.MustCompile(`^连接 MCP Server 失败: (.+)$`)
wxworkUnsupportedOutboundPattern = regexp.MustCompile(`^不支持的企业微信下行消息类型: (.+)$`)
wxworkUnsupportedCurrentOutboundPattern = regexp.MustCompile(`^当前暂不支持企业微信下行消息类型: (.+)$`)
)
var knownMessages = map[string]map[string]string{
LocaleEnUS: {
"未登录或登录已过期": "Your session has expired. Please sign in again.",
"无权限执行该操作": "You do not have permission to perform this action.",
"无权限操作": "You do not have permission to perform this action.",
"参数不合法": "Invalid request parameters.",
"路径参数错误": "Invalid path parameter.",
"状态值不合法": "Invalid status value.",
"用户名和密码不能为空": "Enter both username and password.",
"用户名或密码错误": "The username or password is incorrect.",
"登录失败次数过多,请稍后再试": "Too many failed sign-in attempts. Please try again later.",
"用户不存在": "User not found.",
"用户不存在或已被禁用": "The user does not exist or has been disabled.",
"用户名不能为空": "Enter a username.",
"用户名已存在": "This username is already in use.",
"手机号已存在": "This phone number is already in use.",
"邮箱已存在": "This email address is already in use.",
"新密码不能为空": "Enter a new password.",
"禁用角色不允许分配": "Disabled roles cannot be assigned.",
"角色不存在": "Role not found.",
"角色名称和编码不能为空": "Enter both role name and role code.",
"角色编码已存在": "This role code is already in use.",
"系统内置角色不允许删除": "Built-in system roles cannot be deleted.",
"角色已被用户使用,无法删除": "This role is assigned to users and cannot be deleted.",
"权限不存在": "Permission not found.",
"会话不存在": "Conversation not found.",
"会话筛选项不合法": "Invalid conversation filter.",
"只有待接入会话允许分配": "Only queued conversations can be assigned.",
"只有待接入会话允许自动分配": "Only queued conversations can be auto-assigned.",
"当前会话已分配客服": "This conversation has already been assigned.",
"当前暂不在人工客服服务时间内": "Human support is currently outside service hours.",
"目标客服不能为空": "Select a target agent.",
"目标客服不存在": "Target agent not found.",
"无权转接该会话": "You do not have permission to transfer this conversation.",
"只有处理中会话允许转接": "Only active conversations can be transferred.",
"当前会话未分配客服": "This conversation is not assigned to an agent.",
"目标客服不能与当前指派人相同": "The target agent must be different from the current assignee.",
"无权访问该会话": "You do not have permission to access this conversation.",
"当前状态不允许关闭会话": "This conversation cannot be closed in its current status.",
"关闭原因不能为空": "Enter a close reason.",
"无权关闭该会话": "You do not have permission to close this conversation.",
"已关闭的会话无法关联客户": "Closed conversations cannot be linked to customers.",
"无权限关联该会话": "You do not have permission to link this conversation.",
"外部用户标识不能为空": "External user ID is required.",
"接入渠道未初始化": "The channel is not initialized.",
"接入渠道不存在或已停用": "The channel does not exist or has been disabled.",
"接入渠道异常": "Channel error.",
"channel not found": "Channel not found.",
"该渠道不支持开放客服配置": "This channel does not support public support configuration.",
"外部身份未初始化": "External identity is not initialized.",
"conversationId不能为空": "Conversation ID is required.",
"请选择上传图片": "Choose an image to upload.",
"仅支持上传图片文件": "Only image files are supported.",
"请选择上传附件": "Choose an attachment to upload.",
"请选择上传文件": "Choose a file to upload.",
"文件不存在": "File not found.",
"文件不可访问": "This file cannot be accessed.",
"上传文件超过大小限制": "The uploaded file exceeds the size limit.",
"图片资源不存在": "Image asset not found.",
"当前暂不支持该存储类型的文件读取": "Reading files from this storage provider is not supported yet.",
"客户不存在": "Customer not found.",
"客户名称不能为空": "Enter a customer name.",
"所属公司不存在": "Company not found.",
"公司不存在": "Company not found.",
"公司名称不能为空": "Enter a company name.",
"公司名称已存在": "This company name is already in use.",
"工单不存在": "Ticket not found.",
"工单标题不能为空": "Enter a ticket title.",
"工单描述不能为空": "Enter a ticket description.",
"工单来源不合法": "Invalid ticket source.",
"工单状态不合法": "Invalid ticket status.",
"处理进展不能为空": "Enter a progress update.",
"负责人不存在": "Assignee not found.",
"会话与客户不匹配": "The conversation does not belong to this customer.",
"视图名称不能为空": "Enter a view name.",
"视图筛选条件格式不正确": "The view filter format is invalid.",
"视图不存在": "View not found.",
"标签不存在": "Tag not found.",
"标签名称不能为空": "Enter a tag name.",
"父标签不存在": "Parent tag not found.",
"同级下已存在相同名称的标签": "A tag with this name already exists at the same level.",
"不能将标签设为自己的子标签": "A tag cannot be moved under itself.",
"该标签下存在子标签,无法删除": "This tag has child tags and cannot be deleted.",
"该标签已关联会话,无法删除": "This tag is linked to conversations and cannot be deleted.",
"该标签已关联工单,无法删除": "This tag is linked to tickets and cannot be deleted.",
"快捷回复不存在": "Quick reply not found.",
"客服档案不存在": "Agent profile not found.",
"请选择关联用户": "Select a user to link.",
"关联用户不存在": "Linked user not found.",
"请选择所属客服组": "Select an agent team.",
"所属客服组不存在": "Agent team not found.",
"客服工号和展示名不能为空": "Enter both agent ID and display name.",
"该用户已存在客服档案": "This user already has an agent profile.",
"客服工号已存在": "This agent ID is already in use.",
"客服状态不合法": "Invalid agent status.",
"最大并发接待数不能小于 0": "Maximum concurrent conversations cannot be less than 0.",
"客服组不存在": "Agent team not found.",
"客服组排班不存在": "Agent team schedule not found.",
"知识库不存在": "Knowledge base not found.",
"FAQ不存在": "FAQ not found.",
"FAQ知识库不支持文档": "FAQ knowledge bases do not support documents.",
"文档不存在": "Document not found.",
"内容类型不支持": "Unsupported content type.",
"问题不能为空": "Enter a question.",
"答案不能为空": "Enter an answer.",
"相似问格式不合法": "Invalid similar-question format.",
"当前知识库不是FAQ知识库": "This knowledge base is not an FAQ knowledge base.",
"documentId或faqId不能为空": "Document ID or FAQ ID is required.",
"检索日志不存在": "Retrieval log not found.",
"indexStatus参数不合法": "Invalid index status.",
"AI配置不存在": "AI configuration not found.",
"启用中的AI配置不允许删除": "Enabled AI configurations cannot be deleted.",
"配置名称不能为空": "Enter a configuration name.",
"供应商不能为空": "Select a provider.",
"基础地址不能为空": "Enter a base URL.",
"模型类型不能为空": "Select a model type.",
"模型名称不能为空": "Enter a model name.",
"AI Agent 不存在": "AI Agent not found.",
"AI Agent不存在或未启用": "AI Agent not found or not enabled.",
"AI Agent 不存在或已停用": "AI Agent not found or disabled.",
"AI Agent关联的AI配置不存在": "The AI configuration linked to this AI Agent does not exist.",
"CheckPoint 不存在": "Checkpoint not found.",
"CheckPoint 与 AI Agent 不匹配": "The checkpoint does not belong to this AI Agent.",
"会话与 AI Agent 不匹配": "The conversation does not belong to this AI Agent.",
"Skill 不存在或未启用": "Skill not found or not enabled.",
"Skill 不存在": "Skill not found.",
"Skill ID 不合法": "Invalid Skill ID.",
"Skill 编码已存在": "This Skill code is already in use.",
"Skill 编码不能为空": "Enter a Skill code.",
"Skill 名称不能为空": "Enter a Skill name.",
"技能说明不能为空": "Enter a Skill description.",
"JSON 数组格式不合法": "Invalid JSON array format.",
"已删除的 Skill 不能直接修改状态,请先恢复": "Restore this deleted Skill before changing its status.",
"请使用删除接口处理删除状态": "Use the delete endpoint to mark a Skill as deleted.",
"仅已删除的 Skill 支持恢复": "Only deleted Skills can be restored.",
"MCP未启用": "MCP is not enabled.",
"serverCode不能为空": "Server code is required.",
"MCP endpoint不能为空": "MCP endpoint is required.",
"MCP服务配置不存在": "MCP server configuration not found.",
"MCP服务未启用": "MCP server is not enabled.",
"toolName不能为空": "Tool name is required.",
"toolCode不能为空": "Tool code is required.",
"toolCode格式不合法": "Invalid tool code format.",
"toolCode 绑定的 MCP 服务不存在或未启用": "The MCP server bound to this tool code does not exist or is not enabled.",
"文本内容不能为空": "Text content is required.",
"文本列表不能为空": "Text list is required.",
"密码长度不合法": "Invalid password length.",
"AI Agent 不存在或未启用": "AI Agent not found or not enabled.",
"AI Agent 名称不能为空": "Enter an AI Agent name.",
"AI Agent 名称已存在": "This AI Agent name is already in use.",
"AI 配置不存在": "AI configuration not found.",
"AI 配置不能为空": "Select an AI configuration.",
"AI 配置未启用": "The AI configuration is not enabled.",
"Agent 运行日志不存在": "Agent run log not found.",
"Direct Tool 的 toolCode 与 serverCode 不一致": "Direct Tool toolCode does not match serverCode.",
"Direct Tool 的 toolCode 与 toolName 不一致": "Direct Tool toolCode does not match toolName.",
"Direct Tool 的 toolCode 格式不合法": "Invalid Direct Tool toolCode format.",
"Direct Tool 的 toolCode、serverCode 和 toolName 不能为空": "Direct Tool toolCode, serverCode, and toolName are required.",
"Direct Tools 仅允许配置 MCP 工具": "Direct Tools can only contain MCP tools.",
"Direct Tools 配置格式不合法": "Invalid Direct Tools configuration format.",
"Graph Tools 仅允许配置 Graph Tool": "Graph Tools can only contain Graph tools.",
"Graph Tools 配置格式不合法": "Invalid Graph Tools configuration format.",
"HTML消息中的图片必须使用已上传文件": "Images in HTML messages must use uploaded files.",
"OIDC id_token 不存在": "OIDC id_token is missing.",
"OIDC clientId 未配置": "OIDC clientId is not configured.",
"OIDC clientSecret 未配置": "OIDC clientSecret is not configured.",
"OIDC issuer 未配置": "OIDC issuer is not configured.",
"OIDC redirectUrl 未配置": "OIDC redirectUrl is not configured.",
"OIDC 授权 code 不能为空": "OIDC authorization code is required.",
"OIDC 用户信息不存在": "OIDC user information not found.",
"OIDC 用户标识不存在": "OIDC user identifier not found.",
"OIDC 登录密钥未配置": "OIDC sign-in secret is not configured.",
"OIDC 登录未启用": "OIDC sign-in is not enabled.",
"OIDC 登录状态无效或已过期": "OIDC sign-in state is invalid or has expired.",
"OIDC 账号绑定的系统用户不存在": "The system user linked to this OIDC account does not exist.",
"OSS accessKeyId 未配置": "OSS accessKeyId is not configured.",
"OSS accessKeySecret 未配置": "OSS accessKeySecret is not configured.",
"OSS bucket 未配置": "OSS bucket is not configured.",
"OSS endpoint 未配置": "OSS endpoint is not configured.",
"Skill 未启用": "Skill is not enabled.",
"Web渠道配置 position 只能为 left 或 right": "Web channel position must be left or right.",
"Web渠道配置不合法": "Invalid web channel configuration.",
"aiAgentId不能为空": "AI Agent ID is required.",
"checkPointId不能为空": "Checkpoint ID is required.",
"customerId 必填": "Customer ID is required.",
"openKfID不能为空": "openKfID is required.",
"openKfId 已被其他渠道使用": "This openKfId is already used by another channel.",
"skillCode不能为空": "Skill code is required.",
"ticket 不能为空": "Ticket is required.",
"userMessage不能为空": "User message is required.",
"登录结果不能为空": "Sign-in result is required.",
"不支持的发送人类型": "Unsupported sender type.",
"不支持的已读操作类型": "Unsupported read operation type.",
"不支持的文件存储类型": "Unsupported file storage type.",
"不能添加或修改历史日期的排班": "Schedules cannot be added or changed for past dates.",
"事务上下文不能为空": "Transaction context is required.",
"仅允许撤回自己发送的消息": "You can only recall messages you sent.",
"仅支持撤回客服消息": "Only agent messages can be recalled.",
"仅能指定一条主联系方式": "Only one primary contact method can be set.",
"企业微信 openKfID 不能为空": "WeCom openKfID is required.",
"企业微信媒体ID不能为空": "WeCom media ID is required.",
"企业微信客户ID不能为空": "WeCom customer ID is required.",
"企业微信手机号已被系统用户占用": "This WeCom phone number is already used by a system user.",
"企业微信接入渠道未绑定AI Agent": "The WeCom channel is not linked to an AI Agent.",
"企业微信接入渠道绑定的AI Agent不存在或已禁用": "The AI Agent linked to this WeCom channel does not exist or has been disabled.",
"企业微信未启用或配置不完整": "WeCom is not enabled or its configuration is incomplete.",
"企业微信消息ID不能为空": "WeCom message ID is required.",
"企业微信渠道配置不合法": "Invalid WeCom channel configuration.",
"企业微信渠道配置缺少 openKfId": "WeCom channel configuration is missing openKfId.",
"企业微信登录状态无效": "WeCom sign-in state is invalid.",
"企业微信登录回调地址未配置": "WeCom sign-in callback URL is not configured.",
"企业微信 AgentID 未配置": "WeCom AgentID is not configured.",
"微信授权 code 不能为空": "WeChat authorization code is required.",
"当前登录身份不是企业内部成员": "The current sign-in identity is not an internal enterprise member.",
"企业微信返回的消息ID为空": "WeCom returned an empty message ID.",
"企业微信返回的图片 media_id 为空": "WeCom returned an empty image media_id.",
"图片消息缺少 assetId": "Image message is missing assetId.",
"平台消息不存在": "Platform message not found.",
"文本消息内容为空": "Text message content is empty.",
"HTML 消息内容为空": "HTML message content is empty.",
"企业微信用户ID已被系统用户名占用": "This WeCom user ID is already used as a system username.",
"企业微信用户ID获取失败": "Failed to get the WeCom user ID.",
"企业微信用户信息不存在": "WeCom user information not found.",
"企业微信登录密钥未配置": "WeCom sign-in secret is not configured.",
"企业微信登录未启用": "WeCom sign-in is not enabled.",
"企业微信登录状态无效或已过期": "WeCom sign-in state is invalid or has expired.",
"企业微信账号绑定的系统用户不存在": "The system user linked to this WeCom account does not exist.",
"企业微信邮箱已被系统用户占用": "This WeCom email address is already used by a system user.",
"会话已关闭": "This conversation is closed.",
"会话未分配客服,暂不允许发送消息": "This conversation has not been assigned to an agent, so messages cannot be sent yet.",
"兜底策略不合法": "Invalid fallback policy.",
"分块策略不支持": "Unsupported chunking strategy.",
"创建会话失败": "Failed to create the conversation.",
"单条排班记录不能跨天": "A single schedule entry cannot span multiple days.",
"只有待接入未分配会话允许自动分配": "Only queued unassigned conversations can be auto-assigned.",
"回复超时秒数不能小于 0": "Reply timeout seconds cannot be less than 0.",
"存在冲突排班,请先处理冲突": "There are conflicting schedules. Resolve the conflicts first.",
"存在无效工单标签": "Some ticket tags are invalid.",
"存在未启用的工单标签": "Some ticket tags are disabled.",
"客服会话不能为空": "Support session is required.",
"客服会话参数不完整": "Support session parameters are incomplete.",
"客服会话密钥未配置": "Support session secret is not configured.",
"客服会话已过期": "The support session has expired.",
"客服会话校验失败": "Support session verification failed.",
"客服组下仍有关联 AI Agent,无法删除": "This agent team is linked to AI Agents and cannot be deleted.",
"客服组下仍有关联客服档案,无法删除": "This agent team has linked agent profiles and cannot be deleted.",
"客服组下仍有关联组排班,无法删除": "This agent team has schedules and cannot be deleted.",
"客服组名称不能为空": "Enter an agent team name.",
"客服组名称已存在": "This agent team name is already in use.",
"客服组未启用": "Agent team is not enabled.",
"客服组状态不合法": "Invalid agent team status.",
"已有接入渠道绑定该 AI Agent,无法删除": "This AI Agent is linked to channels and cannot be deleted.",
"当前 OIDC 绑定已停用": "The current OIDC binding has been disabled.",
"当前企业微信绑定已停用": "The current WeCom binding has been disabled.",
"当前会话不处于 AI 接待状态": "This conversation is not currently handled by AI.",
"当前会话已分配给其他客服": "This conversation has been assigned to another agent.",
"当前会话已由人工客服接管": "This conversation has already been taken over by a human agent.",
"当前渠道不支持用户 JWT Secret": "This channel does not support a user JWT secret.",
"当前系统账号已被禁用": "The current system account has been disabled.",
"微信公众号渠道配置不合法": "Invalid WeChat Official Account channel configuration.",
"接入渠道不存在": "Channel not found.",
"接收人不能为空": "Recipient is required.",
"文档知识库不能使用FAQ分块策略": "Document knowledge bases cannot use the FAQ chunking strategy.",
"目标工具未被当前会话授权": "The target tool is not authorized for the current conversation.",
"tool_search 只支持调用 MCP toolCode": "tool_search only supports calling MCP toolCode.",
"时间格式错误": "Invalid time format.",
"星期必须在 1 到 7 之间": "Weekday must be between 1 and 7.",
"服务模式不合法": "Invalid service mode.",
"未找到匹配的企业微信接入渠道": "No matching WeCom channel was found.",
"未生成任何排班": "No schedules were generated.",
"未配置可用的 AI 配置": "No available AI configuration is configured.",
"未配置可用的 Embedding 模型": "No available embedding model is configured.",
"标题和内容不能为空": "Enter both title and content.",
"消息不存在": "Message not found.",
"消息内容不能为空": "Message content is required.",
"消息已撤回": "This message has been recalled.",
"渠道名称不能为空": "Enter a channel name.",
"渠道标识已存在": "This channel code is already in use.",
"渠道状态不合法": "Invalid channel status.",
"渠道类型不合法": "Invalid channel type.",
"用户名称不能为空": "User name is required.",
"用户标识不能为空": "User ID is required.",
"用户身份不能为空": "User identity is required.",
"用户身份已过期": "User identity has expired.",
"用户身份校验失败": "User identity verification failed.",
"用户身份校验未配置": "User identity verification is not configured.",
"登录票据无效或已过期": "The sign-in ticket is invalid or has expired.",
"知识库下存在FAQ,无法删除": "This knowledge base contains FAQs and cannot be deleted.",
"知识库下存在文档,无法删除": "This knowledge base contains documents and cannot be deleted.",
"知识库不能为空": "Knowledge base is required.",
"知识库未启用": "Knowledge base is not enabled.",
"知识库类型不支持": "Unsupported knowledge base type.",
"目录名称不能为空": "Directory name is required.",
"目录不存在": "Directory not found.",
"目录不能移动到其他知识库": "Directories cannot be moved to another knowledge base.",
"存在子目录的目录不能移动到二级目录": "A directory with subdirectories cannot be moved to the second level.",
"同级下已存在相同名称的目录": "A directory with this name already exists at the same level.",
"该目录下存在子目录,无法删除": "This directory contains subdirectories and cannot be deleted.",
"该目录下存在文档,无法删除": "This directory contains documents and cannot be deleted.",
"该目录下存在FAQ,无法删除": "This directory contains FAQs and cannot be deleted.",
"只能调整同知识库同级目录排序": "Only directories in the same knowledge base and same level can be reordered.",
"知识库目录不存在": "Knowledge base directory not found.",
"知识库目录不属于当前知识库": "This directory does not belong to the current knowledge base.",
"知识库目录不可用": "This knowledge base directory is unavailable.",
"不能将目录设为自己的子目录": "A directory cannot be moved under itself.",
"父目录不存在": "Parent directory not found.",
"父目录不属于当前知识库": "The parent directory does not belong to the current knowledge base.",
"知识库目录最多支持二级": "Knowledge base directories support at most two levels.",
"请选择要移动的文档": "Select documents to move.",
"只能移动当前知识库下的文档": "Only documents in the current knowledge base can be moved.",
"请选择要删除的文档": "Select documents to delete.",
"请选择要移动的FAQ": "Select FAQs to move.",
"只能移动当前知识库下的FAQ": "Only FAQs in the current knowledge base can be moved.",
"请选择要删除的FAQ": "Select FAQs to delete.",
"导入模式不合法": "Invalid import mode.",
"请选择导入文件": "Choose a file to import.",
"仅支持.xlsx文件": "Only .xlsx files are supported.",
"导入文件读取失败": "Failed to read the import file.",
"Excel文件解析失败": "Failed to parse the Excel file.",
"Excel文件为空": "The Excel file is empty.",
"Excel文件读取失败": "Failed to read the Excel file.",
"缺少标准问题列": "The standard question column is missing.",
"缺少答案列": "The answer column is missing.",
"标准问题已存在,已跳过": "The standard question already exists and was skipped.",
"问题不能超过500字": "The question cannot exceed 500 characters.",
"组长用户不存在": "Team lead user not found.",
"结束日期必须晚于或等于开始日期": "End date must be later than or equal to start date.",
"结束时间必须晚于开始时间": "End time must be later than start time.",
"联系方式不存在": "Contact method not found.",
"联系方式不能为空": "Contact method is required.",
"联系方式类型不合法": "Invalid contact method type.",
"该客服组在所选时间段已存在排班": "This agent team already has a schedule in the selected time period.",
"该联系方式已存在": "This contact method already exists.",
"请至少选择一个知识库": "Select at least one knowledge base.",
"请选择 AI Agent": "Select an AI Agent.",
"请选择客服组": "Select an agent team.",
"请选择星期": "Select a weekday.",
"转人工模式不合法": "Invalid human handoff mode.",
"通知不存在": "Notification not found.",
"附件不存在": "Attachment not found.",
"附件尚未上传完成": "The attachment has not finished uploading.",
"附件消息 payload 格式错误": "Invalid attachment message payload format.",
"附件消息缺少 assetId": "Attachment message is missing assetId.",
"附件消息缺少 payload": "Attachment message is missing payload.",
"默认客服组待接入池模式必须至少选择一个客服组": "Default team queue mode requires at least one agent team.",
},
}
func TranslateKnownMessage(locale string, message string) string {
normalized := NormalizeLocale(locale)
if normalized == LocaleZhCN {
return message
}
if translations, ok := knownMessages[normalized]; ok {
if translated, exists := translations[message]; exists {
return translated
}
}
if translated, ok := translateKnownPattern(normalized, message); ok {
return translated
}
return message
}
func translateKnownPattern(locale string, message string) (string, bool) {
if locale != LocaleEnUS {
return "", false
}
if matches := maxScheduleBatchMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("You can generate at most %s schedule entries at a time.", matches[1]), true
}
if matches := knowledgeBaseReferencedByAgentPattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("This knowledge base is referenced by AI Agent %q. Remove the binding first.", matches[1]), true
}
if matches := knowledgeBaseReferencedByAgentsPattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("This knowledge base is referenced by %s AI Agents. Remove the bindings first.", matches[1]), true
}
if matches := agentTeamScheduleConflictPattern.FindStringSubmatch(message); len(matches) == 3 {
return fmt.Sprintf("This agent team already has a schedule from %s to %s.", matches[1], matches[2]), true
}
if matches := knowledgeFAQImportDuplicatePattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("The standard question is duplicated in the same file. It first appeared on row %s.", matches[1]), true
}
if matches := requiredParamMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("Parameter %q is required.", matches[1]), true
}
if matches := mcpListToolsMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("Failed to list MCP tools: %s", matches[1]), true
}
if matches := mcpCallToolMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("Failed to call MCP tool: %s", matches[1]), true
}
if matches := mcpConnectServerMessagePattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("Failed to connect to MCP Server: %s", matches[1]), true
}
if matches := wxworkUnsupportedOutboundPattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("Unsupported WeCom outbound message type: %s", matches[1]), true
}
if matches := wxworkUnsupportedCurrentOutboundPattern.FindStringSubmatch(message); len(matches) == 2 {
return fmt.Sprintf("The current WeCom outbound message type is not supported yet: %s", matches[1]), true
}
if strings.HasPrefix(message, "开始时间格式错误,请使用 ") {
return translateTimeFormatMessage(message, "开始时间格式错误,请使用 ", "start time")
}
if strings.HasPrefix(message, "结束时间格式错误,请使用 ") {
return translateTimeFormatMessage(message, "结束时间格式错误,请使用 ", "end time")
}
return "", false
}
func translateTimeFormatMessage(message string, prefix string, field string) (string, bool) {
format := strings.TrimPrefix(message, prefix)
if format == message || format == "" {
return "", false
}
format = strings.ReplaceAll(format, " 或 ", " or ")
return fmt.Sprintf("Invalid %s format. Use %s.", field, format), true
}
+97
View File
@@ -0,0 +1,97 @@
package i18nx
import (
"embed"
"fmt"
"log/slog"
"path/filepath"
"strings"
"sync"
"github.com/gin-gonic/gin"
"gopkg.in/yaml.v3"
)
//go:embed locales/*.yml
var localeFiles embed.FS
var (
messagesOnce sync.Once
messages map[string]map[string]string
)
func T(ctx *gin.Context, key string, args ...any) string {
if ctx == nil {
return Getf(DefaultLocale, key, args...)
}
return Getf(Locale(ctx), key, args...)
}
func TLocale(locale string, key string, args ...any) string {
return Getf(locale, key, args...)
}
func Get(key string) string {
return Getf(DefaultLocale, key)
}
func Getf(locale string, key string, args ...any) string {
format := lookup(NormalizeLocale(locale), key)
if len(args) == 0 {
return format
}
return fmt.Sprintf(format, args...)
}
func lookup(locale string, key string) string {
loadMessages()
if value := lookupLocale(locale, key); value != "" {
return value
}
if locale != DefaultLocale {
if value := lookupLocale(DefaultLocale, key); value != "" {
return value
}
}
slog.Warn("translation key not found", "key", key, "locale", locale)
return key
}
func lookupLocale(locale string, key string) string {
if values, ok := messages[locale]; ok {
return values[key]
}
slog.Warn("locale not found", "locale", locale)
return ""
}
func loadMessages() {
messagesOnce.Do(func() {
loaded := make(map[string]map[string]string)
entries, err := localeFiles.ReadDir("locales")
if err != nil {
slog.Error("read locale files failed", "err", err)
messages = loaded
return
}
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".yml") {
continue
}
path := filepath.Join("locales", entry.Name())
data, err := localeFiles.ReadFile(path)
if err != nil {
slog.Error("read locale file failed", "file", path, "err", err)
continue
}
values := make(map[string]string)
if err := yaml.Unmarshal(data, &values); err != nil {
slog.Error("parse locale file failed", "file", path, "err", err)
continue
}
locale := strings.TrimSuffix(entry.Name(), ".yml")
loaded[NormalizeLocale(locale)] = values
}
messages = loaded
})
}
@@ -1,8 +0,0 @@
["error.auth.expired"]
other = "Your session has expired. Please sign in again."
["error.notFound"]
other = "Not found"
["error.path.invalid"]
other = "Invalid path parameter."
@@ -1,8 +0,0 @@
["error.auth.expired"]
other = "未登录或登录已过期"
["error.notFound"]
other = "Not found"
["error.path.invalid"]
other = "路径参数错误"
+358
View File
@@ -0,0 +1,358 @@
error.e0001: "AI Agent not found"
error.e0002: "AI Agent not found."
error.e0003: "AI Agent not found or disabled."
error.e0004: "AI Agent not found or not enabled."
error.e0005: "Enter an AI Agent name."
error.e0006: "This AI Agent name is already in use."
error.e0007: "AI Agent not found or not enabled."
error.e0008: "The AI configuration linked to this AI Agent does not exist."
error.e0009: "AI configuration not found."
error.e0010: "Select an AI configuration."
error.e0011: "The AI configuration is not enabled."
error.e0012: "AI configuration not found."
error.e0013: "Agent run log not found."
error.e0014: "Checkpoint not found."
error.e0015: "The checkpoint does not belong to this AI Agent."
error.e0016: "Direct Tool toolCode does not match serverCode."
error.e0017: "Direct Tool toolCode does not match toolName."
error.e0018: "Invalid Direct Tool toolCode format."
error.e0019: "Direct Tool toolCode, serverCode, and toolName are required."
error.e0020: "Direct Tools can only contain MCP tools."
error.e0021: "Invalid Direct Tools configuration format."
error.e0022: "The Excel file is empty."
error.e0023: "Failed to parse the Excel file."
error.e0024: "Failed to read the Excel file."
error.e0025: "FAQ not found."
error.e0026: "FAQ knowledge bases do not support documents."
error.e0027: "Graph Tools can only contain Graph tools."
error.e0028: "Invalid Graph Tools configuration format."
error.e0029: "HTML message content is empty."
error.e0030: "Images in HTML messages must use uploaded files."
error.e0031: "Invalid JSON array format."
error.e0032: "MCP endpoint is required."
error.e0033: "MCP server is not enabled."
error.e0034: "MCP server configuration not found."
error.e0035: "MCP is not enabled."
error.e0036: "OIDC clientId is not configured."
error.e0037: "OIDC clientSecret is not configured."
error.e0038: "OIDC id_token is missing."
error.e0039: "OIDC issuer is not configured."
error.e0040: "OIDC redirectUrl is not configured."
error.e0041: "OIDC authorization code is required."
error.e0042: "OIDC user information not found."
error.e0043: "OIDC user identifier not found."
error.e0044: "OIDC sign-in secret is not configured."
error.e0045: "OIDC sign-in is not enabled."
error.e0046: "OIDC sign-in state is invalid or has expired."
error.e0047: "The system user linked to this OIDC account does not exist."
error.e0048: "OSS accessKeyId is not configured."
error.e0049: "OSS accessKeySecret is not configured."
error.e0050: "OSS bucket is not configured."
error.e0051: "OSS endpoint is not configured."
error.e0052: "Invalid Skill ID."
error.e0053: "Skill not found."
error.e0054: "Skill not found or not enabled."
error.e0055: "Enter a Skill name."
error.e0056: "Skill is not enabled."
error.e0057: "Enter a Skill code."
error.e0058: "This Skill code is already in use."
error.e0059: "Web channel position must be left or right."
error.e0060: "Invalid web channel configuration."
error.e0061: "AI Agent ID is required."
error.e0062: "Channel not found."
error.e0063: "Checkpoint ID is required."
error.e0064: "Conversation ID is required."
error.e0065: "Customer ID is required."
error.e0066: "Document ID or FAQ ID is required."
error.e0067: "Invalid index status."
error.e0068: "openKfID is required."
error.e0069: "This openKfId is already used by another channel."
error.e0070: "Server code is required."
error.e0071: "Skill code is required."
error.e0072: "Ticket is required."
error.e0073: "The MCP server bound to this tool code does not exist or is not enabled."
error.e0074: "Tool code is required."
error.e0075: "Invalid tool code format."
error.e0076: "Tool name is required."
error.e0077: "tool_search only supports calling MCP toolCode."
error.e0078: "User message is required."
error.e0079: "The uploaded file exceeds the size limit."
error.e0080: "Unsupported sender type."
error.e0081: "Unsupported read operation type."
error.e0082: "Unsupported file storage type."
error.e0083: "A tag cannot be moved under itself."
error.e0084: "A directory cannot be moved under itself."
error.e0085: "Schedules cannot be added or changed for past dates."
error.e0086: "Transaction context is required."
error.e0087: "You can only recall messages you sent."
error.e0088: "Only deleted Skills can be restored."
error.e0089: "Only .xlsx files are supported."
error.e0090: "Only image files are supported."
error.e0091: "Only agent messages can be recalled."
error.e0092: "Only one primary contact method can be set."
error.e0093: "WeCom AgentID is not configured."
error.e0094: "WeCom openKfID is required."
error.e0095: "WeCom media ID is required."
error.e0096: "WeCom customer ID is required."
error.e0097: "This WeCom phone number is already used by a system user."
error.e0098: "The WeCom channel is not linked to an AI Agent."
error.e0099: "The AI Agent linked to this WeCom channel does not exist or has been disabled."
error.e0100: "WeCom is not enabled or its configuration is incomplete."
error.e0101: "WeCom message ID is required."
error.e0102: "Invalid WeCom channel configuration."
error.e0103: "WeCom channel configuration is missing openKfId."
error.e0104: "This WeCom user ID is already used as a system username."
error.e0105: "Failed to get the WeCom user ID."
error.e0106: "WeCom user information not found."
error.e0107: "WeCom sign-in callback URL is not configured."
error.e0108: "WeCom sign-in secret is not configured."
error.e0109: "WeCom sign-in is not enabled."
error.e0110: "WeCom sign-in state is invalid."
error.e0111: "WeCom sign-in state is invalid or has expired."
error.e0112: "The system user linked to this WeCom account does not exist."
error.e0113: "WeCom returned an empty image media_id."
error.e0114: "WeCom returned an empty message ID."
error.e0115: "This WeCom email address is already used by a system user."
error.e0116: "Conversation not found."
error.e0117: "The conversation does not belong to this AI Agent."
error.e0118: "The conversation does not belong to this customer."
error.e0119: "This conversation is closed."
error.e0120: "This conversation has not been assigned to an agent, so messages cannot be sent yet."
error.e0121: "Invalid conversation filter."
error.e0122: "Select a provider."
error.e0123: "Invalid fallback policy."
error.e0124: "Company not found."
error.e0125: "Enter a company name."
error.e0126: "This company name is already in use."
error.e0127: "Linked user not found."
error.e0128: "Enter a close reason."
error.e0129: "Unsupported content type."
error.e0130: "Unsupported chunking strategy."
error.e0131: "Failed to create the conversation."
error.e0132: "A single schedule entry cannot span multiple days."
error.e0133: "Invalid request parameters."
error.e0134: "Only active conversations can be transferred."
error.e0135: "Only queued conversations can be assigned."
error.e0136: "Only queued conversations can be auto-assigned."
error.e0137: "Only queued unassigned conversations can be auto-assigned."
error.e0138: "Only FAQs in the current knowledge base can be moved."
error.e0139: "Only documents in the current knowledge base can be moved."
error.e0140: "Only directories in the same knowledge base and same level can be reordered."
error.e0141: "A tag with this name already exists at the same level."
error.e0142: "A directory with this name already exists at the same level."
error.e0143: "Enabled AI configurations cannot be deleted."
error.e0144: "Reply timeout seconds cannot be less than 0."
error.e0145: "Image message is missing assetId."
error.e0146: "Image asset not found."
error.e0147: "Enter a base URL."
error.e0148: "Enter a progress update."
error.e0149: "External user ID is required."
error.e0150: "External identity is not initialized."
error.e0151: "There are conflicting schedules. Resolve the conflicts first."
error.e0152: "A directory with subdirectories cannot be moved to the second level."
error.e0153: "Some ticket tags are invalid."
error.e0154: "Some ticket tags are disabled."
error.e0155: "Customer not found."
error.e0156: "Enter a customer name."
error.e0157: "Support session is required."
error.e0158: "Support session parameters are incomplete."
error.e0159: "Support session secret is not configured."
error.e0160: "The support session has expired."
error.e0161: "Support session verification failed."
error.e0162: "Enter both agent ID and display name."
error.e0163: "This agent ID is already in use."
error.e0164: "Agent profile not found."
error.e0165: "Invalid agent status."
error.e0166: "This agent team is linked to AI Agents and cannot be deleted."
error.e0167: "This agent team has linked agent profiles and cannot be deleted."
error.e0168: "This agent team has schedules and cannot be deleted."
error.e0169: "Agent team not found."
error.e0170: "Enter an agent team name."
error.e0171: "This agent team name is already in use."
error.e0172: "Agent team schedule not found."
error.e0173: "Agent team is not enabled."
error.e0174: "Invalid agent team status."
error.e0175: "Invalid password length."
error.e0176: "Failed to read the import file."
error.e0177: "Invalid import mode."
error.e0178: "Ticket not found."
error.e0179: "Enter a ticket description."
error.e0180: "Invalid ticket source."
error.e0181: "Enter a ticket title."
error.e0182: "Invalid ticket status."
error.e0183: "Closed conversations cannot be linked to customers."
error.e0184: "Restore this deleted Skill before changing its status."
error.e0185: "This AI Agent is linked to channels and cannot be deleted."
error.e0186: "Platform message not found."
error.e0187: "The current OIDC binding has been disabled."
error.e0188: "The current WeCom binding has been disabled."
error.e0189: "This conversation is not currently handled by AI."
error.e0190: "This conversation has already been assigned."
error.e0191: "This conversation has been assigned to another agent."
error.e0192: "This conversation has already been taken over by a human agent."
error.e0193: "This conversation is not assigned to an agent."
error.e0194: "Human support is currently outside service hours."
error.e0195: "Reading files from this storage provider is not supported yet."
error.e0196: "This channel does not support a user JWT secret."
error.e0197: "This conversation cannot be closed in its current status."
error.e0198: "The current sign-in identity is not an internal enterprise member."
error.e0199: "This knowledge base is not an FAQ knowledge base."
error.e0200: "The current system account has been disabled."
error.e0201: "Invalid WeChat Official Account channel configuration."
error.e0202: "WeChat authorization code is required."
error.e0203: "Quick reply not found."
error.e0204: "Company not found."
error.e0205: "Agent team not found."
error.e0206: "This phone number is already in use."
error.e0207: "Enter a Skill description."
error.e0208: "Channel not found."
error.e0209: "The channel does not exist or has been disabled."
error.e0210: "Channel error."
error.e0211: "The channel is not initialized."
error.e0212: "Recipient is required."
error.e0213: "This file cannot be accessed."
error.e0214: "File not found."
error.e0215: "Text content is required."
error.e0216: "Text list is required."
error.e0217: "Text message content is empty."
error.e0218: "Document not found."
error.e0219: "Document knowledge bases cannot use the FAQ chunking strategy."
error.e0220: "Enter a new password."
error.e0221: "You do not have permission to close this conversation."
error.e0222: "You do not have permission to access this conversation."
error.e0223: "You do not have permission to transfer this conversation."
error.e0224: "You do not have permission to link this conversation."
error.e0225: "You do not have permission to perform this action."
error.e0226: "You do not have permission to perform this action."
error.e0227: "Invalid time format."
error.e0228: "Weekday must be between 1 and 7."
error.e0229: "Maximum concurrent conversations cannot be less than 0."
error.e0230: "Invalid service mode."
error.e0231: "No matching WeCom channel was found."
error.e0232: "No schedules were generated."
error.auth.expired: "Your session has expired. Please sign in again."
error.e0234: "No available AI configuration is configured."
error.e0235: "No available embedding model is configured."
error.e0236: "Permission not found."
error.e0237: "The standard question already exists and was skipped."
error.e0238: "Tag not found."
error.e0239: "Enter a tag name."
error.e0240: "Enter both title and content."
error.e0241: "Retrieval log not found."
error.e0242: "Enter a model name."
error.e0243: "Select a model type."
error.e0244: "Message not found."
error.e0245: "Message content is required."
error.e0246: "This message has been recalled."
error.e0247: "Enter a channel name."
error.e0248: "This channel code is already in use."
error.e0249: "Invalid channel status."
error.e0250: "Invalid channel type."
error.e0251: "Parent tag not found."
error.e0252: "Parent directory not found."
error.e0253: "The parent directory does not belong to the current knowledge base."
error.e0254: "Invalid status value."
error.e0255: "User not found."
error.e0256: "The user does not exist or has been disabled."
error.e0257: "Enter a username."
error.e0258: "Enter both username and password."
error.e0259: "This username is already in use."
error.e0260: "The username or password is incorrect."
error.e0261: "User name is required."
error.e0262: "User ID is required."
error.e0263: "User identity is required."
error.e0264: "User identity has expired."
error.e0265: "User identity verification failed."
error.e0266: "User identity verification is not configured."
error.e0267: "Login credential has been invalidated."
error.e0268: "Login credential has expired."
error.e0269: "Invalid login credential."
error.e0270: "Too many failed sign-in attempts. Please try again later."
error.e0271: "The sign-in ticket is invalid or has expired."
error.e0272: "Sign-in result is required."
error.e0273: "Directory not found."
error.e0274: "Directories cannot be moved to another knowledge base."
error.e0275: "Directory name is required."
error.e0276: "Target agent not found."
error.e0277: "The target agent must be different from the current assignee."
error.e0278: "Select a target agent."
error.e0279: "The target tool is not authorized for the current conversation."
error.e0280: "Invalid similar-question format."
error.e0281: "This knowledge base contains FAQs and cannot be deleted."
error.e0282: "This knowledge base contains documents and cannot be deleted."
error.e0283: "Knowledge base not found."
error.e0284: "Knowledge base is required."
error.e0285: "Knowledge base is not enabled."
error.e0286: "This knowledge base directory is unavailable."
error.e0287: "Knowledge base directory not found."
error.e0288: "This directory does not belong to the current knowledge base."
error.e0289: "Knowledge base directories support at most two levels."
error.e0290: "Unsupported knowledge base type."
error.e0291: "Disabled roles cannot be assigned."
error.e0292: "Enter an answer."
error.e0293: "Built-in system roles cannot be deleted."
error.e0294: "Team lead user not found."
error.e0295: "End date must be later than or equal to start date."
error.e0296: "End time must be later than start time."
error.e0297: "The standard question column is missing."
error.e0298: "The answer column is missing."
error.e0299: "Contact method not found."
error.e0300: "Contact method is required."
error.e0301: "Invalid contact method type."
error.e0302: "View not found."
error.e0303: "Enter a view name."
error.e0304: "The view filter format is invalid."
error.e0305: "Role not found."
error.e0306: "Enter both role name and role code."
error.e0307: "This role is assigned to users and cannot be deleted."
error.e0308: "This role code is already in use."
error.e0309: "This agent team already has a schedule in the selected time period."
error.e0310: "This tag has child tags and cannot be deleted."
error.e0311: "This tag is linked to conversations and cannot be deleted."
error.e0312: "This tag is linked to tickets and cannot be deleted."
error.e0313: "This channel does not support public support configuration."
error.e0314: "This user already has an agent profile."
error.e0315: "This directory contains FAQs and cannot be deleted."
error.e0316: "This directory contains subdirectories and cannot be deleted."
error.e0317: "This directory contains documents and cannot be deleted."
error.e0318: "This contact method already exists."
error.e0319: "Use the delete endpoint to mark a Skill as deleted."
error.e0320: "Select at least one knowledge base."
error.e0321: "Select an AI Agent."
error.e0322: "Choose an image to upload."
error.e0323: "Choose a file to upload."
error.e0324: "Choose an attachment to upload."
error.e0325: "Select a user to link."
error.e0326: "Select an agent team."
error.e0327: "Choose a file to import."
error.e0328: "Select an agent team."
error.e0329: "Select a weekday."
error.e0330: "Select FAQs to delete."
error.e0331: "Select documents to delete."
error.e0332: "Select FAQs to move."
error.e0333: "Select documents to move."
error.e0334: "Assignee not found."
error.path.invalid: "Invalid path parameter."
error.e0336: "Invalid human handoff mode."
error.e0337: "Notification not found."
error.e0338: "This email address is already in use."
error.e0339: "Enter a configuration name."
error.e0340: "Enter a question."
error.e0341: "The question cannot exceed 500 characters."
error.e0342: "Attachment not found."
error.e0343: "The attachment has not finished uploading."
error.e0344: "Invalid attachment message payload format."
error.e0345: "Attachment message is missing assetId."
error.e0346: "Attachment message is missing payload."
error.e0347: "Default team queue mode requires at least one agent team."
error.notFound: "Not found"
error.knowledgeBase.referencedByAgent: "This knowledge base is referenced by AI Agent \"%s\". Remove the binding first."
error.knowledgeBase.referencedByAgents: "This knowledge base is referenced by %d AI Agents. Remove the bindings first."
error.agentTeamSchedule.batchLimit: "You can generate at most %d schedule entries at a time."
error.agentTeamSchedule.conflictRange: "This agent team already has a schedule from %s to %s."
error.knowledgeFAQImport.duplicateQuestionInFile: "The standard question is duplicated in the same file. It first appeared on row %d."
error.mcp.listToolsFailed: "Failed to list MCP tools: %v"
error.mcp.callToolFailed: "Failed to call MCP tool: %v"
error.mcp.connectServerFailed: "Failed to connect to MCP Server: %v"
error.wxwork.unsupportedOutboundMessageType: "Unsupported WeCom outbound message type: %s"
error.wxwork.currentUnsupportedOutboundMessageType: "The current WeCom outbound message type is not supported yet: %s"
+358
View File
@@ -0,0 +1,358 @@
error.e0001: "AI Agent not found"
error.e0002: "AI Agent 不存在"
error.e0003: "AI Agent 不存在或已停用"
error.e0004: "AI Agent 不存在或未启用"
error.e0005: "AI Agent 名称不能为空"
error.e0006: "AI Agent 名称已存在"
error.e0007: "AI Agent不存在或未启用"
error.e0008: "AI Agent关联的AI配置不存在"
error.e0009: "AI 配置不存在"
error.e0010: "AI 配置不能为空"
error.e0011: "AI 配置未启用"
error.e0012: "AI配置不存在"
error.e0013: "Agent 运行日志不存在"
error.e0014: "CheckPoint 不存在"
error.e0015: "CheckPoint 与 AI Agent 不匹配"
error.e0016: "Direct Tool 的 toolCode 与 serverCode 不一致"
error.e0017: "Direct Tool 的 toolCode 与 toolName 不一致"
error.e0018: "Direct Tool 的 toolCode 格式不合法"
error.e0019: "Direct Tool 的 toolCode、serverCode 和 toolName 不能为空"
error.e0020: "Direct Tools 仅允许配置 MCP 工具"
error.e0021: "Direct Tools 配置格式不合法"
error.e0022: "Excel文件为空"
error.e0023: "Excel文件解析失败"
error.e0024: "Excel文件读取失败"
error.e0025: "FAQ不存在"
error.e0026: "FAQ知识库不支持文档"
error.e0027: "Graph Tools 仅允许配置 Graph Tool"
error.e0028: "Graph Tools 配置格式不合法"
error.e0029: "HTML 消息内容为空"
error.e0030: "HTML消息中的图片必须使用已上传文件"
error.e0031: "JSON 数组格式不合法"
error.e0032: "MCP endpoint不能为空"
error.e0033: "MCP服务未启用"
error.e0034: "MCP服务配置不存在"
error.e0035: "MCP未启用"
error.e0036: "OIDC clientId 未配置"
error.e0037: "OIDC clientSecret 未配置"
error.e0038: "OIDC id_token 不存在"
error.e0039: "OIDC issuer 未配置"
error.e0040: "OIDC redirectUrl 未配置"
error.e0041: "OIDC 授权 code 不能为空"
error.e0042: "OIDC 用户信息不存在"
error.e0043: "OIDC 用户标识不存在"
error.e0044: "OIDC 登录密钥未配置"
error.e0045: "OIDC 登录未启用"
error.e0046: "OIDC 登录状态无效或已过期"
error.e0047: "OIDC 账号绑定的系统用户不存在"
error.e0048: "OSS accessKeyId 未配置"
error.e0049: "OSS accessKeySecret 未配置"
error.e0050: "OSS bucket 未配置"
error.e0051: "OSS endpoint 未配置"
error.e0052: "Skill ID 不合法"
error.e0053: "Skill 不存在"
error.e0054: "Skill 不存在或未启用"
error.e0055: "Skill 名称不能为空"
error.e0056: "Skill 未启用"
error.e0057: "Skill 编码不能为空"
error.e0058: "Skill 编码已存在"
error.e0059: "Web渠道配置 position 只能为 left 或 right"
error.e0060: "Web渠道配置不合法"
error.e0061: "aiAgentId不能为空"
error.e0062: "channel not found"
error.e0063: "checkPointId不能为空"
error.e0064: "conversationId不能为空"
error.e0065: "customerId 必填"
error.e0066: "documentId或faqId不能为空"
error.e0067: "indexStatus参数不合法"
error.e0068: "openKfID不能为空"
error.e0069: "openKfId 已被其他渠道使用"
error.e0070: "serverCode不能为空"
error.e0071: "skillCode不能为空"
error.e0072: "ticket 不能为空"
error.e0073: "toolCode 绑定的 MCP 服务不存在或未启用"
error.e0074: "toolCode不能为空"
error.e0075: "toolCode格式不合法"
error.e0076: "toolName不能为空"
error.e0077: "tool_search 只支持调用 MCP toolCode"
error.e0078: "userMessage不能为空"
error.e0079: "上传文件超过大小限制"
error.e0080: "不支持的发送人类型"
error.e0081: "不支持的已读操作类型"
error.e0082: "不支持的文件存储类型"
error.e0083: "不能将标签设为自己的子标签"
error.e0084: "不能将目录设为自己的子目录"
error.e0085: "不能添加或修改历史日期的排班"
error.e0086: "事务上下文不能为空"
error.e0087: "仅允许撤回自己发送的消息"
error.e0088: "仅已删除的 Skill 支持恢复"
error.e0089: "仅支持.xlsx文件"
error.e0090: "仅支持上传图片文件"
error.e0091: "仅支持撤回客服消息"
error.e0092: "仅能指定一条主联系方式"
error.e0093: "企业微信 AgentID 未配置"
error.e0094: "企业微信 openKfID 不能为空"
error.e0095: "企业微信媒体ID不能为空"
error.e0096: "企业微信客户ID不能为空"
error.e0097: "企业微信手机号已被系统用户占用"
error.e0098: "企业微信接入渠道未绑定AI Agent"
error.e0099: "企业微信接入渠道绑定的AI Agent不存在或已禁用"
error.e0100: "企业微信未启用或配置不完整"
error.e0101: "企业微信消息ID不能为空"
error.e0102: "企业微信渠道配置不合法"
error.e0103: "企业微信渠道配置缺少 openKfId"
error.e0104: "企业微信用户ID已被系统用户名占用"
error.e0105: "企业微信用户ID获取失败"
error.e0106: "企业微信用户信息不存在"
error.e0107: "企业微信登录回调地址未配置"
error.e0108: "企业微信登录密钥未配置"
error.e0109: "企业微信登录未启用"
error.e0110: "企业微信登录状态无效"
error.e0111: "企业微信登录状态无效或已过期"
error.e0112: "企业微信账号绑定的系统用户不存在"
error.e0113: "企业微信返回的图片 media_id 为空"
error.e0114: "企业微信返回的消息ID为空"
error.e0115: "企业微信邮箱已被系统用户占用"
error.e0116: "会话不存在"
error.e0117: "会话与 AI Agent 不匹配"
error.e0118: "会话与客户不匹配"
error.e0119: "会话已关闭"
error.e0120: "会话未分配客服,暂不允许发送消息"
error.e0121: "会话筛选项不合法"
error.e0122: "供应商不能为空"
error.e0123: "兜底策略不合法"
error.e0124: "公司不存在"
error.e0125: "公司名称不能为空"
error.e0126: "公司名称已存在"
error.e0127: "关联用户不存在"
error.e0128: "关闭原因不能为空"
error.e0129: "内容类型不支持"
error.e0130: "分块策略不支持"
error.e0131: "创建会话失败"
error.e0132: "单条排班记录不能跨天"
error.e0133: "参数不合法"
error.e0134: "只有处理中会话允许转接"
error.e0135: "只有待接入会话允许分配"
error.e0136: "只有待接入会话允许自动分配"
error.e0137: "只有待接入未分配会话允许自动分配"
error.e0138: "只能移动当前知识库下的FAQ"
error.e0139: "只能移动当前知识库下的文档"
error.e0140: "只能调整同知识库同级目录排序"
error.e0141: "同级下已存在相同名称的标签"
error.e0142: "同级下已存在相同名称的目录"
error.e0143: "启用中的AI配置不允许删除"
error.e0144: "回复超时秒数不能小于 0"
error.e0145: "图片消息缺少 assetId"
error.e0146: "图片资源不存在"
error.e0147: "基础地址不能为空"
error.e0148: "处理进展不能为空"
error.e0149: "外部用户标识不能为空"
error.e0150: "外部身份未初始化"
error.e0151: "存在冲突排班,请先处理冲突"
error.e0152: "存在子目录的目录不能移动到二级目录"
error.e0153: "存在无效工单标签"
error.e0154: "存在未启用的工单标签"
error.e0155: "客户不存在"
error.e0156: "客户名称不能为空"
error.e0157: "客服会话不能为空"
error.e0158: "客服会话参数不完整"
error.e0159: "客服会话密钥未配置"
error.e0160: "客服会话已过期"
error.e0161: "客服会话校验失败"
error.e0162: "客服工号和展示名不能为空"
error.e0163: "客服工号已存在"
error.e0164: "客服档案不存在"
error.e0165: "客服状态不合法"
error.e0166: "客服组下仍有关联 AI Agent,无法删除"
error.e0167: "客服组下仍有关联客服档案,无法删除"
error.e0168: "客服组下仍有关联组排班,无法删除"
error.e0169: "客服组不存在"
error.e0170: "客服组名称不能为空"
error.e0171: "客服组名称已存在"
error.e0172: "客服组排班不存在"
error.e0173: "客服组未启用"
error.e0174: "客服组状态不合法"
error.e0175: "密码长度不合法"
error.e0176: "导入文件读取失败"
error.e0177: "导入模式不合法"
error.e0178: "工单不存在"
error.e0179: "工单描述不能为空"
error.e0180: "工单来源不合法"
error.e0181: "工单标题不能为空"
error.e0182: "工单状态不合法"
error.e0183: "已关闭的会话无法关联客户"
error.e0184: "已删除的 Skill 不能直接修改状态,请先恢复"
error.e0185: "已有接入渠道绑定该 AI Agent,无法删除"
error.e0186: "平台消息不存在"
error.e0187: "当前 OIDC 绑定已停用"
error.e0188: "当前企业微信绑定已停用"
error.e0189: "当前会话不处于 AI 接待状态"
error.e0190: "当前会话已分配客服"
error.e0191: "当前会话已分配给其他客服"
error.e0192: "当前会话已由人工客服接管"
error.e0193: "当前会话未分配客服"
error.e0194: "当前暂不在人工客服服务时间内"
error.e0195: "当前暂不支持该存储类型的文件读取"
error.e0196: "当前渠道不支持用户 JWT Secret"
error.e0197: "当前状态不允许关闭会话"
error.e0198: "当前登录身份不是企业内部成员"
error.e0199: "当前知识库不是FAQ知识库"
error.e0200: "当前系统账号已被禁用"
error.e0201: "微信公众号渠道配置不合法"
error.e0202: "微信授权 code 不能为空"
error.e0203: "快捷回复不存在"
error.e0204: "所属公司不存在"
error.e0205: "所属客服组不存在"
error.e0206: "手机号已存在"
error.e0207: "技能说明不能为空"
error.e0208: "接入渠道不存在"
error.e0209: "接入渠道不存在或已停用"
error.e0210: "接入渠道异常"
error.e0211: "接入渠道未初始化"
error.e0212: "接收人不能为空"
error.e0213: "文件不可访问"
error.e0214: "文件不存在"
error.e0215: "文本内容不能为空"
error.e0216: "文本列表不能为空"
error.e0217: "文本消息内容为空"
error.e0218: "文档不存在"
error.e0219: "文档知识库不能使用FAQ分块策略"
error.e0220: "新密码不能为空"
error.e0221: "无权关闭该会话"
error.e0222: "无权访问该会话"
error.e0223: "无权转接该会话"
error.e0224: "无权限关联该会话"
error.e0225: "无权限执行该操作"
error.e0226: "无权限操作"
error.e0227: "时间格式错误"
error.e0228: "星期必须在 1 到 7 之间"
error.e0229: "最大并发接待数不能小于 0"
error.e0230: "服务模式不合法"
error.e0231: "未找到匹配的企业微信接入渠道"
error.e0232: "未生成任何排班"
error.auth.expired: "未登录或登录已过期"
error.e0234: "未配置可用的 AI 配置"
error.e0235: "未配置可用的 Embedding 模型"
error.e0236: "权限不存在"
error.e0237: "标准问题已存在,已跳过"
error.e0238: "标签不存在"
error.e0239: "标签名称不能为空"
error.e0240: "标题和内容不能为空"
error.e0241: "检索日志不存在"
error.e0242: "模型名称不能为空"
error.e0243: "模型类型不能为空"
error.e0244: "消息不存在"
error.e0245: "消息内容不能为空"
error.e0246: "消息已撤回"
error.e0247: "渠道名称不能为空"
error.e0248: "渠道标识已存在"
error.e0249: "渠道状态不合法"
error.e0250: "渠道类型不合法"
error.e0251: "父标签不存在"
error.e0252: "父目录不存在"
error.e0253: "父目录不属于当前知识库"
error.e0254: "状态值不合法"
error.e0255: "用户不存在"
error.e0256: "用户不存在或已被禁用"
error.e0257: "用户名不能为空"
error.e0258: "用户名和密码不能为空"
error.e0259: "用户名已存在"
error.e0260: "用户名或密码错误"
error.e0261: "用户名称不能为空"
error.e0262: "用户标识不能为空"
error.e0263: "用户身份不能为空"
error.e0264: "用户身份已过期"
error.e0265: "用户身份校验失败"
error.e0266: "用户身份校验未配置"
error.e0267: "登录凭证已失效"
error.e0268: "登录凭证已过期"
error.e0269: "登录凭证无效"
error.e0270: "登录失败次数过多,请稍后再试"
error.e0271: "登录票据无效或已过期"
error.e0272: "登录结果不能为空"
error.e0273: "目录不存在"
error.e0274: "目录不能移动到其他知识库"
error.e0275: "目录名称不能为空"
error.e0276: "目标客服不存在"
error.e0277: "目标客服不能与当前指派人相同"
error.e0278: "目标客服不能为空"
error.e0279: "目标工具未被当前会话授权"
error.e0280: "相似问格式不合法"
error.e0281: "知识库下存在FAQ,无法删除"
error.e0282: "知识库下存在文档,无法删除"
error.e0283: "知识库不存在"
error.e0284: "知识库不能为空"
error.e0285: "知识库未启用"
error.e0286: "知识库目录不可用"
error.e0287: "知识库目录不存在"
error.e0288: "知识库目录不属于当前知识库"
error.e0289: "知识库目录最多支持二级"
error.e0290: "知识库类型不支持"
error.e0291: "禁用角色不允许分配"
error.e0292: "答案不能为空"
error.e0293: "系统内置角色不允许删除"
error.e0294: "组长用户不存在"
error.e0295: "结束日期必须晚于或等于开始日期"
error.e0296: "结束时间必须晚于开始时间"
error.e0297: "缺少标准问题列"
error.e0298: "缺少答案列"
error.e0299: "联系方式不存在"
error.e0300: "联系方式不能为空"
error.e0301: "联系方式类型不合法"
error.e0302: "视图不存在"
error.e0303: "视图名称不能为空"
error.e0304: "视图筛选条件格式不正确"
error.e0305: "角色不存在"
error.e0306: "角色名称和编码不能为空"
error.e0307: "角色已被用户使用,无法删除"
error.e0308: "角色编码已存在"
error.e0309: "该客服组在所选时间段已存在排班"
error.e0310: "该标签下存在子标签,无法删除"
error.e0311: "该标签已关联会话,无法删除"
error.e0312: "该标签已关联工单,无法删除"
error.e0313: "该渠道不支持开放客服配置"
error.e0314: "该用户已存在客服档案"
error.e0315: "该目录下存在FAQ,无法删除"
error.e0316: "该目录下存在子目录,无法删除"
error.e0317: "该目录下存在文档,无法删除"
error.e0318: "该联系方式已存在"
error.e0319: "请使用删除接口处理删除状态"
error.e0320: "请至少选择一个知识库"
error.e0321: "请选择 AI Agent"
error.e0322: "请选择上传图片"
error.e0323: "请选择上传文件"
error.e0324: "请选择上传附件"
error.e0325: "请选择关联用户"
error.e0326: "请选择客服组"
error.e0327: "请选择导入文件"
error.e0328: "请选择所属客服组"
error.e0329: "请选择星期"
error.e0330: "请选择要删除的FAQ"
error.e0331: "请选择要删除的文档"
error.e0332: "请选择要移动的FAQ"
error.e0333: "请选择要移动的文档"
error.e0334: "负责人不存在"
error.path.invalid: "路径参数错误"
error.e0336: "转人工模式不合法"
error.e0337: "通知不存在"
error.e0338: "邮箱已存在"
error.e0339: "配置名称不能为空"
error.e0340: "问题不能为空"
error.e0341: "问题不能超过500字"
error.e0342: "附件不存在"
error.e0343: "附件尚未上传完成"
error.e0344: "附件消息 payload 格式错误"
error.e0345: "附件消息缺少 assetId"
error.e0346: "附件消息缺少 payload"
error.e0347: "默认客服组待接入池模式必须至少选择一个客服组"
error.notFound: "数据不存在"
error.knowledgeBase.referencedByAgent: "知识库已被 AI Agent「%s」引用,请先解除绑定"
error.knowledgeBase.referencedByAgents: "知识库已被 %d 个 AI Agent 引用,请先解除绑定"
error.agentTeamSchedule.batchLimit: "单次最多生成 %d 条排班"
error.agentTeamSchedule.conflictRange: "该客服组在 %s 至 %s 已存在排班"
error.knowledgeFAQImport.duplicateQuestionInFile: "同一文件中标准问题重复,首次出现于第%d行"
error.mcp.listToolsFailed: "列出 MCP 工具失败: %v"
error.mcp.callToolFailed: "调用 MCP 工具失败: %v"
error.mcp.connectServerFailed: "连接 MCP Server 失败: %v"
error.wxwork.unsupportedOutboundMessageType: "不支持的企业微信下行消息类型: %s"
error.wxwork.currentUnsupportedOutboundMessageType: "当前暂不支持企业微信下行消息类型: %s"
-44
View File
@@ -1,44 +0,0 @@
package i18nx
import (
"github.com/gin-gonic/gin"
"github.com/nicksnyder/go-i18n/v2/i18n"
)
func T(ctx *gin.Context, messageID string, data map[string]any) string {
if ctx != nil {
if value, exists := ctx.Get(contextLocaleKey); exists {
if locale, ok := value.(string); ok {
return TLocale(locale, messageID, data)
}
}
}
return TLocale(DefaultLocale, messageID, data)
}
func TLocale(locale string, messageID string, data map[string]any) string {
normalized := NormalizeLocale(locale)
message := localize(normalized, messageID, data)
if message != "" {
return message
}
if normalized != DefaultLocale {
message = localize(DefaultLocale, messageID, data)
if message != "" {
return message
}
}
return messageID
}
func localize(locale string, messageID string, data map[string]any) string {
localizer := i18n.NewLocalizer(Bundle(), locale, DefaultLocale)
message, err := localizer.Localize(&i18n.LocalizeConfig{
MessageID: messageID,
TemplateData: data,
})
if err != nil {
return ""
}
return message
}
+9 -9
View File
@@ -44,10 +44,10 @@ func GetExternalUser(ctx *gin.Context, secret string) (*ExternalUser, error) {
func verifyUserToken(userToken, secret string) (*UserTokenClaims, error) {
if strs.IsBlank(userToken) {
return nil, errorsx.Unauthorized("用户身份不能为空")
return nil, errorsx.UnauthorizedI18n("error.e0263")
}
if strs.IsBlank(secret) {
return nil, errorsx.Unauthorized("用户身份校验未配置")
return nil, errorsx.UnauthorizedI18n("error.e0266")
}
claims := &UserTokenClaims{}
@@ -63,22 +63,22 @@ func verifyUserToken(userToken, secret string) (*UserTokenClaims, error) {
}))
if err != nil {
if errors.Is(err, jwt.ErrTokenExpired) {
return nil, errorsx.Unauthorized("用户身份已过期")
return nil, errorsx.UnauthorizedI18n("error.e0264")
}
return nil, errorsx.Unauthorized("用户身份校验失败")
return nil, errorsx.UnauthorizedI18n("error.e0265")
}
if token == nil || !token.Valid {
return nil, errorsx.Unauthorized("用户身份校验失败")
return nil, errorsx.UnauthorizedI18n("error.e0265")
}
if strs.IsBlank(claims.UserID) {
return nil, errorsx.Unauthorized("用户标识不能为空")
return nil, errorsx.UnauthorizedI18n("error.e0262")
}
if strs.IsBlank(claims.Name) {
return nil, errorsx.Unauthorized("用户名称不能为空")
return nil, errorsx.UnauthorizedI18n("error.e0261")
}
if claims.ExpiresAt == nil {
return nil, errorsx.Unauthorized("用户身份已过期")
return nil, errorsx.UnauthorizedI18n("error.e0264")
}
return claims, nil
@@ -98,7 +98,7 @@ func getUserToken(ctx *gin.Context) string {
func getGuestUser(ctx *gin.Context) (*ExternalUser, error) {
externalID := getExternalID(ctx)
if strs.IsBlank(externalID) {
return nil, errorsx.Unauthorized("用户标识不能为空")
return nil, errorsx.UnauthorizedI18n("error.e0262")
}
return &ExternalUser{
ExternalSource: enums.ExternalSourceGuest,
+4 -4
View File
@@ -38,10 +38,10 @@ func NormalizeMCPToolRequest(item request.AIAgentMCPToolRequest) (request.AIAgen
parsedServerCode, parsedToolName := SplitMCPToolCode(toolCode)
if parsedServerCode != "" && parsedToolName != "" {
if serverCode != "" && !strings.EqualFold(serverCode, parsedServerCode) {
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 serverCode 不一致")
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParamI18n("error.e0016")
}
if toolName != "" && !strings.EqualFold(toolName, parsedToolName) {
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 与 toolName 不一致")
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParamI18n("error.e0017")
}
serverCode = parsedServerCode
toolName = parsedToolName
@@ -53,14 +53,14 @@ func NormalizeMCPToolRequest(item request.AIAgentMCPToolRequest) (request.AIAgen
toolCode = BuildMCPToolCode(serverCode, toolName)
}
if toolCode == "" {
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode、serverCode 和 toolName 不能为空")
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParamI18n("error.e0019")
}
if parsedServerCode, parsedToolName := SplitMCPToolCode(toolCode); parsedServerCode != "" && parsedToolName != "" {
serverCode = parsedServerCode
toolName = parsedToolName
}
if serverCode == "" && toolName == "" && strings.Contains(toolCode, "/") && !strings.HasPrefix(toolCode, "builtin/") {
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParam("Direct Tool 的 toolCode 格式不合法")
return request.AIAgentMCPToolRequest{}, errorsx.InvalidParamI18n("error.e0018")
}
ret := request.AIAgentMCPToolRequest{
ToolCode: toolCode,
+1 -1
View File
@@ -54,7 +54,7 @@ func FormatTime(t time.Time) string {
func GenerateRandomPassword(length int) (string, error) {
if length <= 0 {
return "", errorsx.InvalidParam("密码长度不合法")
return "", errorsx.InvalidParamI18n("error.e0175")
}
const charset = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789"