feat: 前端 UI 国际化,支持中/英/日三语切换

之前语言设置只影响后端 AI 行为,前端 UI 文字始终为中文。
新增轻量 i18n 模块(React Context + 翻译字典),切换语言后所有 UI 文字即时刷新。

- 新增 i18n 核心模块和 zh-CN/en-US/ja-JP 翻译文件(约 72 个 key)
- App.tsx 拆分为 App(Provider)+ AppContent(业务),确保 Hook 可访问 Context
- 所有组件通过 useI18n().t() 获取翻译文本
- errors.ts 改为接受翻译函数参数
- storage.ts saveConfig 时派发事件通知 locale 变化
This commit is contained in:
2026-06-14 14:34:30 +08:00
parent d8bbdb1744
commit e95b1603c1
14 changed files with 418 additions and 71 deletions

View File

@@ -4,6 +4,7 @@
// ============================================================
import { useEffect, useRef, useState } from "react";
import { useI18n } from "../../lib/i18n";
import type { ChatMessage } from "../../types";
import type { ConnectionStatus } from "../../lib/websocket";
@@ -19,6 +20,7 @@ export function ChatPanel({ messages, currentReply, connectionStatus, onSendText
const containerRef = useRef<HTMLDivElement>(null);
const isAutoScroll = useRef(true);
const [inputText, setInputText] = useState("");
const { t } = useI18n();
const isConnected = connectionStatus === "connected";
@@ -56,8 +58,8 @@ export function ChatPanel({ messages, currentReply, connectionStatus, onSendText
return (
<div className="chat-panel chat-panel--empty">
<span className="chat-panel--empty-icon">💬</span>
<p></p>
<span className="chat-panel--empty-hint"> AI </span>
<p>{t("chat.empty.prompt")}</p>
<span className="chat-panel--empty-hint">{t("chat.empty.hint")}</span>
</div>
);
}
@@ -69,15 +71,15 @@ export function ChatPanel({ messages, currentReply, connectionStatus, onSendText
{messages.length === 0 && !currentReply && (
<div className="chat-panel__welcome">
<span className="chat-panel__welcome-icon">💬</span>
<p></p>
<span className="chat-panel__welcome-hint"></span>
<p>{t("chat.welcome.prompt")}</p>
<span className="chat-panel__welcome-hint">{t("chat.welcome.hint")}</span>
</div>
)}
{messages.map((msg, index) => (
<div key={index} className={`chat-message chat-message--${msg.role}`}>
<div className="chat-message__role">
{msg.role === "user" ? "你" : "AI"}
{msg.role === "user" ? t("chat.userLabel") : "AI"}
</div>
<div className="chat-message__content">{msg.content}</div>
{msg.role === "assistant" && msg.tokensUsed !== undefined && (
@@ -110,7 +112,7 @@ export function ChatPanel({ messages, currentReply, connectionStatus, onSendText
<input
type="text"
className="chat-input__field"
placeholder="输入文字对话..."
placeholder={t("chat.input.placeholder")}
value={inputText}
onChange={(e) => setInputText(e.target.value)}
/>
@@ -118,7 +120,7 @@ export function ChatPanel({ messages, currentReply, connectionStatus, onSendText
type="submit"
className="chat-input__send"
disabled={!inputText.trim()}
title="发送"
title={t("chat.send")}
>
</button>