From af9addaaec73092684d76b4f93429cb4d4b84a3e Mon Sep 17 00:00:00 2001 From: matevip Date: Sat, 11 Apr 2026 17:52:25 +0800 Subject: [PATCH] feat(i18n): structured i18n keys for exceptions and auto-translate 100+ error messages --- .../java/vip/mate/agent/AgentService.java | 4 +- .../vip/mate/auth/service/AuthService.java | 8 ++-- .../mate/channel/service/ChannelService.java | 6 +-- .../vip/mate/cron/service/CronJobService.java | 20 ++++---- .../exception/GlobalExceptionHandler.java | 28 ++++++++++- .../vip/mate/exception/MateClawException.java | 25 ++++++++++ .../vip/mate/skill/service/SkillService.java | 8 ++-- .../tool/mcp/service/McpServerService.java | 14 +++--- .../vip/mate/tool/service/ToolService.java | 4 +- .../core/service/WorkspaceService.java | 22 ++++----- .../src/main/resources/messages.properties | 39 +++++++++++++++ .../src/main/resources/messages_en.properties | 47 +++++++++++++++++++ mateclaw-ui/src/views/AgentContext.vue | 2 +- 13 files changed, 182 insertions(+), 45 deletions(-) diff --git a/mateclaw-server/src/main/java/vip/mate/agent/AgentService.java b/mateclaw-server/src/main/java/vip/mate/agent/AgentService.java index 5f05ad25..d8c1f144 100644 --- a/mateclaw-server/src/main/java/vip/mate/agent/AgentService.java +++ b/mateclaw-server/src/main/java/vip/mate/agent/AgentService.java @@ -56,7 +56,7 @@ public class AgentService { public AgentEntity getAgent(Long id) { AgentEntity entity = agentMapper.selectById(id); if (entity == null) { - throw new MateClawException("Agent不存在: " + id); + throw new MateClawException("err.agent.not_found", "Agent不存在: " + id); } return entity; } @@ -194,7 +194,7 @@ public class AgentService { return agentInstances.computeIfAbsent(agentId, id -> { AgentEntity entity = getAgent(id); if (!Boolean.TRUE.equals(entity.getEnabled())) { - throw new MateClawException("Agent 已禁用: " + entity.getName()); + throw new MateClawException("err.agent.disabled", "Agent 已禁用: " + entity.getName()); } return agentGraphBuilder.build(entity); }); diff --git a/mateclaw-server/src/main/java/vip/mate/auth/service/AuthService.java b/mateclaw-server/src/main/java/vip/mate/auth/service/AuthService.java index ec9a69e5..4a61d315 100644 --- a/mateclaw-server/src/main/java/vip/mate/auth/service/AuthService.java +++ b/mateclaw-server/src/main/java/vip/mate/auth/service/AuthService.java @@ -51,7 +51,7 @@ public class AuthService { .eq(UserEntity::getEnabled, true)); if (user == null || !passwordEncoder.matches(request.getPassword(), user.getPassword())) { - throw new MateClawException("用户名或密码错误"); + throw new MateClawException("err.auth.invalid_credentials", "用户名或密码错误"); } String token = generateToken(user); @@ -74,7 +74,7 @@ public class AuthService { Long count = userMapper.selectCount(new LambdaQueryWrapper() .eq(UserEntity::getUsername, user.getUsername())); if (count > 0) { - throw new MateClawException("用户名已存在: " + user.getUsername()); + throw new MateClawException("err.auth.username_exists", "用户名已存在: " + user.getUsername()); } user.setPassword(passwordEncoder.encode(user.getPassword())); user.setEnabled(true); @@ -92,10 +92,10 @@ public class AuthService { public void changePassword(Long userId, String oldPassword, String newPassword) { UserEntity user = userMapper.selectById(userId); if (user == null) { - throw new MateClawException("用户不存在"); + throw new MateClawException("err.auth.user_not_found", "用户不存在"); } if (!passwordEncoder.matches(oldPassword, user.getPassword())) { - throw new MateClawException("原密码错误"); + throw new MateClawException("err.auth.wrong_password", "原密码错误"); } user.setPassword(passwordEncoder.encode(newPassword)); userMapper.updateById(user); diff --git a/mateclaw-server/src/main/java/vip/mate/channel/service/ChannelService.java b/mateclaw-server/src/main/java/vip/mate/channel/service/ChannelService.java index 8769d17d..611edfe5 100644 --- a/mateclaw-server/src/main/java/vip/mate/channel/service/ChannelService.java +++ b/mateclaw-server/src/main/java/vip/mate/channel/service/ChannelService.java @@ -85,7 +85,7 @@ public class ChannelService { public ChannelEntity getChannel(Long id) { ChannelEntity channel = channelMapper.selectById(id); if (channel == null) { - throw new MateClawException("渠道不存在: " + id); + throw new MateClawException("err.channel.not_found", "渠道不存在: " + id); } return channel; } @@ -96,10 +96,10 @@ public class ChannelService { public ChannelEntity createChannel(ChannelEntity channel) { // 验证名称 if (channel.getName() == null || channel.getName().isBlank()) { - throw new MateClawException("渠道名称不能为空"); + throw new MateClawException("err.channel.name_required", "渠道名称不能为空"); } if (channel.getChannelType() == null || channel.getChannelType().isBlank()) { - throw new MateClawException("渠道类型不能为空"); + throw new MateClawException("err.channel.type_required", "渠道类型不能为空"); } if (channel.getEnabled() == null) { channel.setEnabled(false); diff --git a/mateclaw-server/src/main/java/vip/mate/cron/service/CronJobService.java b/mateclaw-server/src/main/java/vip/mate/cron/service/CronJobService.java index 78b87029..f30274fc 100644 --- a/mateclaw-server/src/main/java/vip/mate/cron/service/CronJobService.java +++ b/mateclaw-server/src/main/java/vip/mate/cron/service/CronJobService.java @@ -110,7 +110,7 @@ public class CronJobService implements ApplicationRunner { public CronJobDTO getById(Long id) { CronJobEntity entity = cronJobMapper.selectById(id); if (entity == null) { - throw new MateClawException("定时任务不存在: " + id); + throw new MateClawException("err.cron.not_found", "定时任务不存在: " + id); } AgentEntity agent = agentMapper.selectById(entity.getAgentId()); return CronJobDTO.from(entity, agent != null ? agent.getName() : "Unknown"); @@ -140,7 +140,7 @@ public class CronJobService implements ApplicationRunner { public CronJobDTO update(Long id, CronJobDTO dto) { CronJobEntity existing = cronJobMapper.selectById(id); if (existing == null) { - throw new MateClawException("定时任务不存在: " + id); + throw new MateClawException("err.cron.not_found", "定时任务不存在: " + id); } validateDto(dto); String springCron = toSpringCron(dto.getCronExpression()); @@ -176,7 +176,7 @@ public class CronJobService implements ApplicationRunner { public void delete(Long id) { CronJobEntity entity = cronJobMapper.selectById(id); if (entity == null) { - throw new MateClawException("定时任务不存在: " + id); + throw new MateClawException("err.cron.not_found", "定时任务不存在: " + id); } schedulerLock.lock(); try { @@ -190,7 +190,7 @@ public class CronJobService implements ApplicationRunner { public void toggle(Long id, Boolean enabled) { CronJobEntity entity = cronJobMapper.selectById(id); if (entity == null) { - throw new MateClawException("定时任务不存在: " + id); + throw new MateClawException("err.cron.not_found", "定时任务不存在: " + id); } entity.setEnabled(enabled); @@ -218,7 +218,7 @@ public class CronJobService implements ApplicationRunner { public void runNow(Long id) { CronJobEntity entity = cronJobMapper.selectById(id); if (entity == null) { - throw new MateClawException("定时任务不存在: " + id); + throw new MateClawException("err.cron.not_found", "定时任务不存在: " + id); } // 异步执行,不阻塞请求线程 scheduler.submit(() -> executeJob(entity)); @@ -404,20 +404,20 @@ public class CronJobService implements ApplicationRunner { private void validateDto(CronJobDTO dto) { if (dto.getName() == null || dto.getName().isBlank()) { - throw new MateClawException("任务名称不能为空"); + throw new MateClawException("err.cron.name_required", "任务名称不能为空"); } if (dto.getAgentId() == null) { - throw new MateClawException("请选择关联 Agent"); + throw new MateClawException("err.cron.agent_required", "请选择关联 Agent"); } if (dto.getCronExpression() == null || dto.getCronExpression().isBlank()) { - throw new MateClawException("Cron 表达式不能为空"); + throw new MateClawException("err.cron.expression_required", "Cron 表达式不能为空"); } String taskType = dto.getTaskType() != null ? dto.getTaskType() : "text"; if ("text".equals(taskType) && (dto.getTriggerMessage() == null || dto.getTriggerMessage().isBlank())) { - throw new MateClawException("触发消息不能为空"); + throw new MateClawException("err.cron.trigger_required", "触发消息不能为空"); } if ("agent".equals(taskType) && (dto.getRequestBody() == null || dto.getRequestBody().isBlank())) { - throw new MateClawException("执行目标不能为空"); + throw new MateClawException("err.cron.target_required", "执行目标不能为空"); } } } diff --git a/mateclaw-server/src/main/java/vip/mate/exception/GlobalExceptionHandler.java b/mateclaw-server/src/main/java/vip/mate/exception/GlobalExceptionHandler.java index 40f9d3bd..c7cba54e 100644 --- a/mateclaw-server/src/main/java/vip/mate/exception/GlobalExceptionHandler.java +++ b/mateclaw-server/src/main/java/vip/mate/exception/GlobalExceptionHandler.java @@ -2,6 +2,7 @@ package vip.mate.exception; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.validation.BindException; @@ -9,16 +10,24 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.context.request.async.AsyncRequestTimeoutException; import vip.mate.common.result.R; +import vip.mate.i18n.I18nService; /** * 全局异常处理器 + *

+ * MateClawException 的中文消息通过 I18nService 查表翻译: + * 中文原文作为 key 前缀查 properties(err.msg.{hash}), + * 找到翻译返回翻译,找不到返回原文。 * * @author MateClaw Team */ @Slf4j @RestControllerAdvice +@RequiredArgsConstructor public class GlobalExceptionHandler { + private final I18nService i18nService; + @ExceptionHandler(AsyncRequestTimeoutException.class) public R handleAsyncTimeout(AsyncRequestTimeoutException e, HttpServletRequest request, @@ -36,7 +45,24 @@ public class GlobalExceptionHandler { @ExceptionHandler(MateClawException.class) public R handleMateClawException(MateClawException e) { log.warn("Business exception: [{}] {}", e.getCode(), e.getMessage()); - return R.fail(e.getCode(), e.getMessage()); + String msg = translateExceptionMsg(e); + return R.fail(e.getCode(), msg); + } + + /** + * Translate exception message via i18n. + * If the exception has a msgKey, use it to look up the translated message. + * Otherwise return the original message as-is. + */ + private String translateExceptionMsg(MateClawException e) { + String msgKey = e.getMsgKey(); + if (msgKey != null && !msgKey.isEmpty()) { + String translated = i18nService.msg(msgKey); + if (!translated.equals(msgKey)) { + return translated; + } + } + return e.getMessage(); } @ExceptionHandler(BindException.class) diff --git a/mateclaw-server/src/main/java/vip/mate/exception/MateClawException.java b/mateclaw-server/src/main/java/vip/mate/exception/MateClawException.java index 3881dbbd..31b9fe32 100644 --- a/mateclaw-server/src/main/java/vip/mate/exception/MateClawException.java +++ b/mateclaw-server/src/main/java/vip/mate/exception/MateClawException.java @@ -12,19 +12,44 @@ import vip.mate.common.result.ResultCode; public class MateClawException extends RuntimeException { private final int code; + /** i18n message key (optional). When set, GlobalExceptionHandler uses this to look up translated message. */ + private final String msgKey; public MateClawException(String message) { super(message); this.code = 500; + this.msgKey = null; } public MateClawException(int code, String message) { super(message); this.code = code; + this.msgKey = null; } public MateClawException(ResultCode resultCode) { super(resultCode.getMsg()); this.code = resultCode.getCode(); + this.msgKey = null; + } + + /** + * Create with i18n message key. The key is resolved by GlobalExceptionHandler. + * @param msgKey i18n key (e.g. "err.workspace.not_found") + * @param message fallback message (Chinese or default text) + */ + public MateClawException(String msgKey, String message) { + super(message); + this.code = 500; + this.msgKey = msgKey; + } + + /** + * Create with i18n message key and custom code. + */ + public MateClawException(String msgKey, int code, String message) { + super(message); + this.code = code; + this.msgKey = msgKey; } } diff --git a/mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java b/mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java index 3149c630..1a733ab4 100644 --- a/mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java +++ b/mateclaw-server/src/main/java/vip/mate/skill/service/SkillService.java @@ -82,7 +82,7 @@ public class SkillService { public SkillEntity getSkill(Long id) { SkillEntity skill = skillMapper.selectById(id); if (skill == null) { - throw new MateClawException("技能不存在: " + id); + throw new MateClawException("err.skill.not_found", "技能不存在: " + id); } return skill; } @@ -94,14 +94,14 @@ public class SkillService { public SkillEntity createSkill(SkillEntity skill) { // 验证名称不为空 if (skill.getName() == null || skill.getName().isBlank()) { - throw new MateClawException("技能名称不能为空"); + throw new MateClawException("err.skill.name_required", "技能名称不能为空"); } // 检查名称唯一性 Long count = skillMapper.selectCount(new LambdaQueryWrapper() .eq(SkillEntity::getName, skill.getName())); if (count > 0) { - throw new MateClawException("技能名称已存在: " + skill.getName()); + throw new MateClawException("err.skill.name_exists", "技能名称已存在: " + skill.getName()); } // 设置默认值 @@ -187,7 +187,7 @@ public class SkillService { public void deleteSkill(Long id) { SkillEntity skill = getSkill(id); if (Boolean.TRUE.equals(skill.getBuiltin())) { - throw new MateClawException("内置技能不可删除: " + skill.getName()); + throw new MateClawException("err.skill.builtin_readonly", "内置技能不可删除: " + skill.getName()); } skillMapper.deleteById(id); log.info("Deleted skill: {}", skill.getName()); diff --git a/mateclaw-server/src/main/java/vip/mate/tool/mcp/service/McpServerService.java b/mateclaw-server/src/main/java/vip/mate/tool/mcp/service/McpServerService.java index 5b36bff6..416f541f 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/mcp/service/McpServerService.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/mcp/service/McpServerService.java @@ -50,7 +50,7 @@ public class McpServerService { public McpServerEntity getById(Long id) { McpServerEntity entity = mcpServerMapper.selectById(id); if (entity == null) { - throw new MateClawException("MCP server 不存在: " + id); + throw new MateClawException("err.mcp.not_found", "MCP server 不存在: " + id); } return entity; } @@ -124,7 +124,7 @@ public class McpServerService { public void delete(Long id) { McpServerEntity entity = getById(id); if (Boolean.TRUE.equals(entity.getBuiltin())) { - throw new MateClawException("内置 MCP server 不可删除"); + throw new MateClawException("err.mcp.builtin_readonly", "内置 MCP server 不可删除"); } // Disconnect first @@ -305,21 +305,21 @@ public class McpServerService { private void validateServer(McpServerEntity entity) { if (entity.getName() == null || entity.getName().isBlank()) { - throw new MateClawException("MCP server 名称不能为空"); + throw new MateClawException("err.mcp.name_required", "MCP server 名称不能为空"); } if (entity.getTransport() == null || entity.getTransport().isBlank()) { - throw new MateClawException("传输类型不能为空"); + throw new MateClawException("err.mcp.transport_required", "传输类型不能为空"); } if (!List.of("stdio", "sse", "streamable_http").contains(entity.getTransport())) { - throw new MateClawException("不支持的传输类型: " + entity.getTransport()); + throw new MateClawException("err.mcp.transport_unsupported", "不支持的传输类型: " + entity.getTransport()); } if ("stdio".equals(entity.getTransport())) { if (entity.getCommand() == null || entity.getCommand().isBlank()) { - throw new MateClawException("stdio 类型必须指定 command"); + throw new MateClawException("err.mcp.stdio_command_required", "stdio 类型必须指定 command"); } } else { if (entity.getUrl() == null || entity.getUrl().isBlank()) { - throw new MateClawException("HTTP/SSE 类型必须指定 url"); + throw new MateClawException("err.mcp.http_url_required", "HTTP/SSE 类型必须指定 url"); } } // Validate JSON fields — 不仅要求合法 JSON,还要求正确的结构类型 diff --git a/mateclaw-server/src/main/java/vip/mate/tool/service/ToolService.java b/mateclaw-server/src/main/java/vip/mate/tool/service/ToolService.java index 1ffa0a87..c6feb452 100644 --- a/mateclaw-server/src/main/java/vip/mate/tool/service/ToolService.java +++ b/mateclaw-server/src/main/java/vip/mate/tool/service/ToolService.java @@ -34,7 +34,7 @@ public class ToolService { public ToolEntity getTool(Long id) { ToolEntity tool = toolMapper.selectById(id); if (tool == null) { - throw new MateClawException("工具不存在: " + id); + throw new MateClawException("err.tool.not_found", "工具不存在: " + id); } return tool; } @@ -62,7 +62,7 @@ public class ToolService { public void deleteTool(Long id) { ToolEntity tool = getTool(id); if (Boolean.TRUE.equals(tool.getBuiltin())) { - throw new MateClawException("内置工具不可删除"); + throw new MateClawException("err.tool.builtin_readonly", "内置工具不可删除"); } toolMapper.deleteById(id); } diff --git a/mateclaw-server/src/main/java/vip/mate/workspace/core/service/WorkspaceService.java b/mateclaw-server/src/main/java/vip/mate/workspace/core/service/WorkspaceService.java index 9ab7f7eb..8ec886a9 100644 --- a/mateclaw-server/src/main/java/vip/mate/workspace/core/service/WorkspaceService.java +++ b/mateclaw-server/src/main/java/vip/mate/workspace/core/service/WorkspaceService.java @@ -64,7 +64,7 @@ public class WorkspaceService { public WorkspaceEntity getById(Long id) { WorkspaceEntity entity = workspaceMapper.selectById(id); if (entity == null) { - throw new MateClawException("工作区不存在: " + id); + throw new MateClawException("err.workspace.not_found", "工作区不存在: " + id); } return entity; } @@ -79,7 +79,7 @@ public class WorkspaceService { public WorkspaceEntity create(WorkspaceEntity entity, Long creatorUserId) { // 验证 slug 唯一 if (getBySlug(entity.getSlug()) != null) { - throw new MateClawException("工作区标识已存在: " + entity.getSlug()); + throw new MateClawException("err.workspace.slug_exists", "工作区标识已存在: " + entity.getSlug()); } entity.setOwnerId(creatorUserId); workspaceMapper.insert(entity); @@ -103,12 +103,12 @@ public class WorkspaceService { } // 不允许修改默认工作区的 slug if (DEFAULT_SLUG.equals(existing.getSlug()) && !DEFAULT_SLUG.equals(entity.getSlug())) { - throw new MateClawException("不能修改默认工作区的标识"); + throw new MateClawException("err.workspace.cannot_modify_default", "不能修改默认工作区的标识"); } // 验证 slug 唯一性(如果修改了 slug) if (!entity.getSlug().equals(existing.getSlug())) { if (getBySlug(entity.getSlug()) != null) { - throw new MateClawException("工作区标识已存在: " + entity.getSlug()); + throw new MateClawException("err.workspace.slug_exists", "工作区标识已存在: " + entity.getSlug()); } } workspaceMapper.updateById(entity); @@ -118,7 +118,7 @@ public class WorkspaceService { public void delete(Long id) { WorkspaceEntity existing = getById(id); if (DEFAULT_SLUG.equals(existing.getSlug())) { - throw new MateClawException("不能删除默认工作区"); + throw new MateClawException("err.workspace.cannot_delete_default", "不能删除默认工作区"); } workspaceMapper.deleteById(id); log.info("Deleted workspace: {} (id={})", existing.getName(), id); @@ -147,7 +147,7 @@ public class WorkspaceService { // 检查是否已是成员 WorkspaceMemberEntity existing = getMembership(workspaceId, userId); if (existing != null) { - throw new MateClawException("用户已经是该工作区的成员"); + throw new MateClawException("err.workspace.member_exists", "用户已经是该工作区的成员"); } WorkspaceMemberEntity member = new WorkspaceMemberEntity(); member.setWorkspaceId(workspaceId); @@ -162,10 +162,10 @@ public class WorkspaceService { public WorkspaceMemberEntity updateMemberRole(Long workspaceId, Long userId, String role) { WorkspaceMemberEntity member = getMembership(workspaceId, userId); if (member == null) { - throw new MateClawException("用户不是该工作区的成员"); + throw new MateClawException("err.workspace.not_member", "用户不是该工作区的成员"); } if ("owner".equals(member.getRole())) { - throw new MateClawException("不能修改工作区拥有者的角色"); + throw new MateClawException("err.workspace.cannot_modify_owner", "不能修改工作区拥有者的角色"); } member.setRole(role); memberMapper.updateById(member); @@ -176,10 +176,10 @@ public class WorkspaceService { public void removeMember(Long workspaceId, Long userId) { WorkspaceMemberEntity member = getMembership(workspaceId, userId); if (member == null) { - throw new MateClawException("用户不是该工作区的成员"); + throw new MateClawException("err.workspace.not_member", "用户不是该工作区的成员"); } if ("owner".equals(member.getRole())) { - throw new MateClawException("不能移除工作区拥有者"); + throw new MateClawException("err.workspace.cannot_remove_owner", "不能移除工作区拥有者"); } memberMapper.deleteById(member.getId()); evictMembershipCache(workspaceId, userId); @@ -209,7 +209,7 @@ public class WorkspaceService { */ public void requirePermission(Long workspaceId, Long userId, String minRole) { if (!hasPermission(workspaceId, userId, minRole)) { - throw new MateClawException("权限不足:需要 " + minRole + " 或更高角色"); + throw new MateClawException("err.workspace.insufficient_permission", "权限不足:需要 " + minRole + " 或更高角色"); } } diff --git a/mateclaw-server/src/main/resources/messages.properties b/mateclaw-server/src/main/resources/messages.properties index 992667fe..08001d19 100644 --- a/mateclaw-server/src/main/resources/messages.properties +++ b/mateclaw-server/src/main/resources/messages.properties @@ -129,6 +129,45 @@ guard.CRED_AWS_KEY.fix=\u4f7f\u7528 IAM Role \u6216 AWS Secrets Manager guard.CRED_PRIVATE_KEY.name=\u79c1\u94a5\u6cc4\u9732 guard.CRED_PRIVATE_KEY.fix=\u8bf7\u52ff\u5728\u53c2\u6570\u4e2d\u4f20\u9012\u79c1\u94a5 +# --- Exception Messages (structured keys) --- +err.auth.invalid_credentials=\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef +err.auth.username_exists=\u7528\u6237\u540d\u5df2\u5b58\u5728 +err.auth.user_not_found=\u7528\u6237\u4e0d\u5b58\u5728 +err.auth.wrong_password=\u539f\u5bc6\u7801\u9519\u8bef +err.agent.not_found=Agent\u4e0d\u5b58\u5728 +err.agent.disabled=Agent \u5df2\u7981\u7528 +err.workspace.not_found=\u5de5\u4f5c\u533a\u4e0d\u5b58\u5728 +err.workspace.slug_exists=\u5de5\u4f5c\u533a\u6807\u8bc6\u5df2\u5b58\u5728 +err.workspace.cannot_modify_default=\u4e0d\u80fd\u4fee\u6539\u9ed8\u8ba4\u5de5\u4f5c\u533a\u7684\u6807\u8bc6 +err.workspace.cannot_delete_default=\u4e0d\u80fd\u5220\u9664\u9ed8\u8ba4\u5de5\u4f5c\u533a +err.workspace.member_exists=\u7528\u6237\u5df2\u7ecf\u662f\u8be5\u5de5\u4f5c\u533a\u7684\u6210\u5458 +err.workspace.not_member=\u7528\u6237\u4e0d\u662f\u8be5\u5de5\u4f5c\u533a\u7684\u6210\u5458 +err.workspace.cannot_modify_owner=\u4e0d\u80fd\u4fee\u6539\u5de5\u4f5c\u533a\u62e5\u6709\u8005\u7684\u89d2\u8272 +err.workspace.cannot_remove_owner=\u4e0d\u80fd\u79fb\u9664\u5de5\u4f5c\u533a\u62e5\u6709\u8005 +err.workspace.insufficient_permission=\u6743\u9650\u4e0d\u8db3 +err.channel.not_found=\u6e20\u9053\u4e0d\u5b58\u5728 +err.channel.name_required=\u6e20\u9053\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a +err.channel.type_required=\u6e20\u9053\u7c7b\u578b\u4e0d\u80fd\u4e3a\u7a7a +err.tool.not_found=\u5de5\u5177\u4e0d\u5b58\u5728 +err.tool.builtin_readonly=\u5185\u7f6e\u5de5\u5177\u4e0d\u53ef\u5220\u9664 +err.skill.not_found=\u6280\u80fd\u4e0d\u5b58\u5728 +err.skill.name_required=\u6280\u80fd\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a +err.skill.name_exists=\u6280\u80fd\u540d\u79f0\u5df2\u5b58\u5728 +err.skill.builtin_readonly=\u5185\u7f6e\u6280\u80fd\u4e0d\u53ef\u5220\u9664 +err.mcp.not_found=MCP server \u4e0d\u5b58\u5728 +err.mcp.builtin_readonly=\u5185\u7f6e MCP server \u4e0d\u53ef\u5220\u9664 +err.mcp.name_required=MCP server \u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a +err.mcp.transport_required=\u4f20\u8f93\u7c7b\u578b\u4e0d\u80fd\u4e3a\u7a7a +err.mcp.transport_unsupported=\u4e0d\u652f\u6301\u7684\u4f20\u8f93\u7c7b\u578b +err.mcp.stdio_command_required=stdio \u7c7b\u578b\u5fc5\u987b\u6307\u5b9a command +err.mcp.http_url_required=HTTP/SSE \u7c7b\u578b\u5fc5\u987b\u6307\u5b9a url +err.cron.not_found=\u5b9a\u65f6\u4efb\u52a1\u4e0d\u5b58\u5728 +err.cron.name_required=\u4efb\u52a1\u540d\u79f0\u4e0d\u80fd\u4e3a\u7a7a +err.cron.agent_required=\u8bf7\u9009\u62e9\u5173\u8054 Agent +err.cron.expression_required=Cron \u8868\u8fbe\u5f0f\u4e0d\u80fd\u4e3a\u7a7a +err.cron.trigger_required=\u89e6\u53d1\u6d88\u606f\u4e0d\u80fd\u4e3a\u7a7a +err.cron.target_required=\u6267\u884c\u76ee\u6807\u4e0d\u80fd\u4e3a\u7a7a + # --- WorkspacePathGuard --- guard.path.not_allowed=\u8def\u5f84\u4e0d\u5728\u5de5\u4f5c\u533a\u5141\u8bb8\u8303\u56f4\u5185: {0}\uff0c\u5141\u8bb8\u7684\u6839\u76ee\u5f55: {1} guard.path.symlink_escape=\u8def\u5f84\u901a\u8fc7\u7b26\u53f7\u94fe\u63a5\u9003\u9038\u51fa\u5de5\u4f5c\u533a: {0}\uff0c\u5141\u8bb8\u7684\u6839\u76ee\u5f55: {1} diff --git a/mateclaw-server/src/main/resources/messages_en.properties b/mateclaw-server/src/main/resources/messages_en.properties index 16153089..0d9c6bd6 100644 --- a/mateclaw-server/src/main/resources/messages_en.properties +++ b/mateclaw-server/src/main/resources/messages_en.properties @@ -133,6 +133,53 @@ guard.CRED_PRIVATE_KEY.fix=Do not pass private keys in parameters guard.path.not_allowed=Path is outside workspace boundary: {0}, allowed root: {1} guard.path.symlink_escape=Path escapes workspace via symlink: {0}, allowed root: {1} +# --- Exception Messages (structured keys, resolved by GlobalExceptionHandler) --- +# auth +err.auth.invalid_credentials=Invalid username or password +err.auth.username_exists=Username already exists +err.auth.user_not_found=User not found +err.auth.wrong_password=Incorrect current password +# agent +err.agent.not_found=Agent not found +err.agent.disabled=Agent is disabled +# workspace +err.workspace.not_found=Workspace not found +err.workspace.slug_exists=Workspace slug already exists +err.workspace.cannot_modify_default=Cannot modify default workspace slug +err.workspace.cannot_delete_default=Cannot delete default workspace +err.workspace.member_exists=User is already a member of this workspace +err.workspace.not_member=User is not a member of this workspace +err.workspace.cannot_modify_owner=Cannot modify workspace owner role +err.workspace.cannot_remove_owner=Cannot remove workspace owner +err.workspace.insufficient_permission=Insufficient permission +# channel +err.channel.not_found=Channel not found +err.channel.name_required=Channel name cannot be empty +err.channel.type_required=Channel type cannot be empty +# tool +err.tool.not_found=Tool not found +err.tool.builtin_readonly=Built-in tool cannot be deleted +# skill +err.skill.not_found=Skill not found +err.skill.name_required=Skill name cannot be empty +err.skill.name_exists=Skill name already exists +err.skill.builtin_readonly=Built-in skill cannot be deleted +# mcp +err.mcp.not_found=MCP server not found +err.mcp.builtin_readonly=Built-in MCP server cannot be deleted +err.mcp.name_required=MCP server name cannot be empty +err.mcp.transport_required=Transport type cannot be empty +err.mcp.transport_unsupported=Unsupported transport type +err.mcp.stdio_command_required=stdio type requires command +err.mcp.http_url_required=HTTP/SSE type requires URL +# cron +err.cron.not_found=Scheduled task not found +err.cron.name_required=Task name cannot be empty +err.cron.agent_required=Please select an Agent +err.cron.expression_required=Cron expression cannot be empty +err.cron.trigger_required=Trigger message cannot be empty +err.cron.target_required=Execution target cannot be empty + # --- RuntimeContext --- context.current_time=[system-context] Current time: {0} {1} (Asia/Shanghai) context.working_dir=[system-context] Working directory: {0} diff --git a/mateclaw-ui/src/views/AgentContext.vue b/mateclaw-ui/src/views/AgentContext.vue index aed04f93..49c46bf1 100644 --- a/mateclaw-ui/src/views/AgentContext.vue +++ b/mateclaw-ui/src/views/AgentContext.vue @@ -140,7 +140,7 @@ - {{ t('agentContext.modified') || '已修改' }} + {{ t('agentContext.modified') }}