feat: 支持多语言能力

This commit is contained in:
2026-01-24 10:10:52 +08:00
parent 77c048b6a2
commit e2280b12e2
23 changed files with 2373 additions and 374 deletions

View File

@@ -10,6 +10,7 @@ import { ConfigPanel, type ConfigOption } from "@/components/tools/ConfigPanel";
import { Button } from "@/components/ui/button";
import { useUploadStore } from "@/store/uploadStore";
import { generateId } from "@/lib/utils";
import { useTranslation } from "@/lib/i18n";
import type { UploadedFile, ProcessedFile, VideoFramesConfig } from "@/types";
const videoAccept = {
@@ -24,45 +25,50 @@ const defaultConfig: VideoFramesConfig = {
height: undefined,
};
const configOptions: ConfigOption[] = [
{
id: "fps",
type: "slider",
label: "Frame Rate",
description: "Number of frames to extract per second",
value: defaultConfig.fps,
min: 1,
max: 60,
step: 1,
suffix: " fps",
icon: <Video className="h-4 w-4" />,
},
{
id: "format",
type: "select",
label: "Output Format",
description: "Image format for the extracted frames",
value: defaultConfig.format,
options: [
{ label: "PNG", value: "png" },
{ label: "JPEG", value: "jpeg" },
{ label: "WebP", value: "webp" },
],
},
{
id: "quality",
type: "slider",
label: "Quality",
description: "Image quality (for JPEG and WebP)",
value: defaultConfig.quality,
min: 1,
max: 100,
step: 1,
suffix: "%",
},
];
function useConfigOptions(config: VideoFramesConfig): ConfigOption[] {
const { t } = useTranslation();
return [
{
id: "fps",
type: "slider",
label: t("config.videoFrames.fps"),
description: t("config.videoFrames.fpsDescription"),
value: config.fps,
min: 1,
max: 60,
step: 1,
suffix: " fps",
icon: <Video className="h-4 w-4" />,
},
{
id: "format",
type: "select",
label: t("config.videoFrames.format"),
description: t("config.videoFrames.formatDescription"),
value: config.format,
options: [
{ label: "PNG", value: "png" },
{ label: "JPEG", value: "jpeg" },
{ label: "WebP", value: "webp" },
],
},
{
id: "quality",
type: "slider",
label: t("config.videoFrames.quality"),
description: t("config.videoFrames.qualityDescription"),
value: config.quality,
min: 1,
max: 100,
step: 1,
suffix: "%",
},
];
}
export default function VideoFramesPage() {
const { t } = useTranslation();
const { files, addFile, removeFile, clearFiles, processingStatus, setProcessingStatus } =
useUploadStore();
@@ -99,7 +105,7 @@ export default function VideoFramesPage() {
setProcessingStatus({
status: "uploading",
progress: 0,
message: "Uploading video...",
message: t("processing.uploadingVideo"),
});
try {
@@ -109,14 +115,14 @@ export default function VideoFramesPage() {
setProcessingStatus({
status: "uploading",
progress: i,
message: `Uploading... ${i}%`,
message: t("processing.uploadProgress", { progress: i }),
});
}
setProcessingStatus({
status: "processing",
progress: 0,
message: "Extracting frames...",
message: t("processing.extractingFrames"),
});
// Simulate processing
@@ -125,7 +131,7 @@ export default function VideoFramesPage() {
setProcessingStatus({
status: "processing",
progress: i,
message: `Processing... ${i}%`,
message: t("processing.processProgress", { progress: i }),
});
}
@@ -149,24 +155,24 @@ export default function VideoFramesPage() {
setProcessingStatus({
status: "completed",
progress: 100,
message: "Processing complete!",
message: t("processing.processingComplete"),
});
} catch (error) {
setProcessingStatus({
status: "failed",
progress: 0,
message: "Processing failed",
error: error instanceof Error ? error.message : "Unknown error",
message: t("processing.processingFailed"),
error: error instanceof Error ? error.message : t("processing.unknownError"),
});
}
};
const handleDownload = (fileId: string) => {
console.log("Downloading file:", fileId);
// Implement download logic
};
const canProcess = files.length > 0 && processingStatus.status !== "processing";
const configOptions = useConfigOptions(config);
return (
<div className="p-6">
@@ -181,9 +187,9 @@ export default function VideoFramesPage() {
<Video className="h-6 w-6 text-primary" />
</div>
<div>
<h1 className="text-3xl font-bold">Video to Frames</h1>
<h1 className="text-3xl font-bold">{t("tools.videoFrames.title")}</h1>
<p className="text-muted-foreground">
Extract frames from videos with customizable settings
{t("tools.videoFrames.description")}
</p>
</div>
</div>
@@ -203,8 +209,8 @@ export default function VideoFramesPage() {
/>
<ConfigPanel
title="Export Settings"
description="Configure how frames are extracted"
title={t("config.videoFrames.title")}
description={t("config.videoFrames.description")}
options={configOptions.map((opt) => ({
...opt,
value: config[opt.id as keyof VideoFramesConfig],
@@ -216,7 +222,7 @@ export default function VideoFramesPage() {
{canProcess && (
<Button onClick={handleProcess} size="lg" className="w-full">
<Settings className="mr-2 h-4 w-4" />
Process Video
{t("tools.videoFrames.processVideo")}
</Button>
)}
</div>
@@ -233,12 +239,11 @@ export default function VideoFramesPage() {
{/* Info Card */}
<div className="rounded-lg border border-border/40 bg-card/50 p-6">
<h3 className="mb-3 font-semibold">How it works</h3>
<h3 className="mb-3 font-semibold">{t("tools.videoFrames.howItWorks")}</h3>
<ol className="space-y-2 text-sm text-muted-foreground">
<li>1. Upload your video file (MP4, MOV, AVI, etc.)</li>
<li>2. Configure frame rate, format, and quality</li>
<li>3. Click &quot;Process Video&quot; to start extraction</li>
<li>4. Download the ZIP file with all frames</li>
{(t("tools.videoFrames.steps") as unknown as string[]).map((step, index) => (
<li key={index}>{index + 1}. {step}</li>
))}
</ol>
</div>
</div>