Files
CamTalk/frontend/src/components/CameraManager/index.tsx
cfy666 e95b1603c1 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 变化
2026-06-14 14:34:30 +08:00

62 lines
2.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ============================================================
// CameraManager — 摄像头流采集
// 职责:获取用户摄像头 MediaStream提供给 VideoPreview 和 EdgeProcessor
// ============================================================
import { useCallback, useRef, useState } from "react";
import { useI18n } from "../../lib/i18n";
export interface CameraManagerHandle {
/** 获取当前视频轨道 */
stream: MediaStream | null;
/** 捕获当前帧为 JPEG DataURL */
captureFrame: () => string | null;
}
export function useCamera() {
const { t } = useI18n();
const videoRef = useRef<HTMLVideoElement>(null);
const [stream, setStream] = useState<MediaStream | null>(null);
const [error, setError] = useState<string | null>(null);
const startCamera = useCallback(async () => {
try {
const mediaStream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: "environment", width: 640, height: 480 },
audio: false,
});
setStream(mediaStream);
if (videoRef.current) {
videoRef.current.srcObject = mediaStream;
}
setError(null);
} catch (err) {
const message = err instanceof Error ? err.message : t("error.cameraAccess");
setError(message);
console.error("[Camera] 获取摄像头失败:", err);
}
}, []);
const stopCamera = useCallback(() => {
stream?.getTracks().forEach((track) => track.stop());
setStream(null);
}, [stream]);
/** 从 video 元素捕获当前帧为 JPEG DataURL */
const captureFrame = useCallback((): string | null => {
const video = videoRef.current;
if (!video || video.readyState < 2) return null;
const canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
const ctx = canvas.getContext("2d");
if (!ctx) return null;
ctx.drawImage(video, 0, 0);
return canvas.toDataURL("image/jpeg", 0.7);
}, []);
return { videoRef, stream, error, startCamera, stopCamera, captureFrame };
}