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

@@ -12,6 +12,7 @@ import { getErrorMessage } from "../lib/errors";
import { TTSPlayer } from "../lib/ttsPlayer";
import { showToast } from "../lib/toast";
import { loadConfig, saveConfig } from "../lib/storage";
import { useI18n } from "../lib/i18n";
import { useCamera } from "../components/CameraManager";
import { useMicrophone } from "../components/MicManager";
import { useVAD, sampleFrame, compareFrames } from "../components/EdgeProcessor";
@@ -29,6 +30,7 @@ export interface SessionStats {
}
export function useVisionSession() {
const { t } = useI18n();
const [messages, setMessages] = useState<ChatMessage[]>([]);
const [currentReply, setCurrentReply] = useState<string>("");
const [isProcessing, setIsProcessing] = useState(false);
@@ -84,7 +86,7 @@ export function useVisionSession() {
...prev,
{
role: "user",
content: "👁️ 画面变化检测",
content: t("session.changeDetected"),
timestamp: Date.now(),
},
]);
@@ -199,7 +201,7 @@ export function useVisionSession() {
// 添加用户消息STT 流式结果会逐步更新文本)
setMessages((prev) => [
...prev,
{ role: "user", content: "(语音识别中...", timestamp: Date.now() },
{ role: "user", content: t("session.recognizing"), timestamp: Date.now() },
]);
setIsProcessing(true);
},
@@ -219,7 +221,7 @@ export function useVisionSession() {
if (lastUserIdx >= 0) {
updated[lastUserIdx] = {
...updated[lastUserIdx],
content: msg.text || "(未识别到语音)",
content: msg.text || t("session.noSpeech"),
};
}
return updated;
@@ -271,7 +273,7 @@ export function useVisionSession() {
case "error":
console.error("[Session] 服务端错误:", msg.code, msg.message);
showToast(getErrorMessage(msg.code), "error");
showToast(getErrorMessage(msg.code, t), "error");
setIsProcessing(false);
break;
}
@@ -359,7 +361,7 @@ export function useVisionSession() {
setIsAudioPlaying(false);
// 将未完成的流式内容保存为最终消息
if (currentReply) {
const interrupted = currentReply + "(已打断)";
const interrupted = currentReply + t("session.interrupted");
historyRef.current.push({ role: "assistant", content: interrupted });
setMessages((prev) => [
...prev,