fix(i18n): dynamically set window title from language pack instead of hardcoding

This commit is contained in:
matevip 2026-04-05 16:34:15 +08:00
parent bcf37d95fe
commit c8f3a12925
8 changed files with 55 additions and 2 deletions

View File

@ -0,0 +1,30 @@
package vip.mate.agent.context;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
/**
* 运行时上下文注入器 LLM 消息列表中预注入当前时间等运行时信息
* <p>
* 参考 Claude Code prependUserContext 模式将时间信息作为首条 meta UserMessage 注入
* 而非修改 System Prompt以保持 prompt cache 命中率
*/
public final class RuntimeContextInjector {
private static final ZoneId ZONE = ZoneId.of("Asia/Shanghai");
private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd");
private static final DateTimeFormatter TIME_FMT = DateTimeFormatter.ofPattern("HH:mm");
private RuntimeContextInjector() {
}
/**
* 构建运行时上下文消息包含当前日期和时间
*/
public static String buildContextMessage() {
LocalDateTime now = LocalDateTime.now(ZONE);
return "[system-context] 当前时间: " + now.format(DATE_FMT)
+ " " + now.format(TIME_FMT) + " (Asia/Shanghai)";
}
}

View File

@ -7,6 +7,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
@ -18,6 +19,7 @@ import vip.mate.agent.AgentToolSet;
import vip.mate.agent.GraphEventPublisher;
import vip.mate.agent.graph.NodeStreamingChatHelper;
import vip.mate.agent.context.ConversationWindowManager;
import vip.mate.agent.context.RuntimeContextInjector;
import vip.mate.agent.graph.state.FinishReason;
import vip.mate.agent.graph.state.MateClawStateAccessor;
import vip.mate.agent.graph.state.MateClawStateKeys;
@ -155,6 +157,8 @@ public class ReasoningNode implements NodeAction {
// 构建 Prompt附带工具定义但禁用内部工具执行
List<Message> promptMessages = new ArrayList<>();
promptMessages.add(new SystemMessage(systemPrompt));
// 注入运行时上下文当前时间 LLM 在推理阶段即可感知真实日期
promptMessages.add(new UserMessage(RuntimeContextInjector.buildContextMessage()));
promptMessages.addAll(messages);
ChatOptions options;

View File

@ -16,6 +16,7 @@ import vip.mate.agent.graph.plan.state.PlanStateAccessor;
import vip.mate.agent.graph.plan.state.PlanStateKeys;
import vip.mate.agent.graph.state.MateClawStateKeys;
import vip.mate.agent.context.ConversationWindowManager;
import vip.mate.agent.context.RuntimeContextInjector;
import vip.mate.planning.service.PlanningService;
import java.util.ArrayList;
@ -125,6 +126,8 @@ public class PlanGenerationNode implements NodeAction {
// 构建 prompt 消息列表system + 历史上下文 + 当前规划请求
List<Message> promptMessages = new ArrayList<>();
promptMessages.add(new SystemMessage(systemPrompt + "\n\n" + PLANNING_PROMPT));
// 注入运行时上下文当前时间
promptMessages.add(new UserMessage(RuntimeContextInjector.buildContextMessage()));
// 注入 working context对话历史摘要让规划能感知之前对话的约束和补充条件
String workingContext = accessor.workingContext();

View File

@ -23,6 +23,7 @@ import vip.mate.agent.graph.plan.state.PlanStateAccessor;
import vip.mate.agent.graph.plan.state.PlanStateKeys;
import vip.mate.agent.graph.state.MateClawStateKeys;
import vip.mate.agent.context.ConversationWindowManager;
import vip.mate.agent.context.RuntimeContextInjector;
import vip.mate.agent.graph.executor.ToolExecutionExecutor;
import vip.mate.channel.web.ChatStreamTracker;
import vip.mate.planning.service.PlanningService;
@ -304,6 +305,8 @@ public class StepExecutionNode implements NodeAction {
8. 每一步最多做一个必要的检查和一个必要的执行不要无意义循环
""";
messages.add(new SystemMessage(enhancedSystemPrompt));
// 注入运行时上下文当前时间
messages.add(new UserMessage(RuntimeContextInjector.buildContextMessage()));
// Layer 2: Working context对话历史 + 步骤结果的受控长度摘要
String workingContext = accessor.workingContext();

View File

@ -5,7 +5,8 @@
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { computed, watchEffect } from 'vue'
import { useI18n } from 'vue-i18n'
import en from 'element-plus/es/locale/lang/en'
import zhCn from 'element-plus/es/locale/lang/zh-cn'
import { currentLocale } from '@/i18n'
@ -14,5 +15,11 @@ import { useThemeStore } from '@/stores/useThemeStore'
// Initialize theme applies .dark class to <html> immediately
useThemeStore()
const { t } = useI18n()
watchEffect(() => {
document.title = t('app.title')
})
const elementLocale = computed(() => (currentLocale.value === 'en-US' ? en : zhCn))
</script>

View File

@ -1,4 +1,7 @@
export default {
app: {
title: 'MateClaw - AI Assistant',
},
common: {
save: 'Save',
cancel: 'Cancel',

View File

@ -1,4 +1,7 @@
export default {
app: {
title: 'MateClaw - AI 助手',
},
common: {
save: '保存',
cancel: '取消',

View File

@ -123,7 +123,7 @@
:loading="isGenerating"
:assistant-icon="currentAgent?.icon || '🤖'"
:user-icon="userInitial"
:title="showModelPrompt ? modelPromptTitle : 'MateClaw'"
:title="showModelPrompt ? modelPromptTitle : $t('app.title')"
:subtitle="showModelPrompt ? modelPromptDesc : $t('chat.subtitle')"
:suggestions="showModelPrompt ? [] : suggestions"
@regenerate="handleRegenerate"