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:
@@ -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 {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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...))
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 = "路径参数错误"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user