fix: 修复服务端渲染时的翻译不匹配问题

This commit is contained in:
2026-01-24 16:38:47 +08:00
parent e2280b12e2
commit b7402edf6a
11 changed files with 319 additions and 184 deletions

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useCallback } from "react";
import { useState, useCallback, useEffect } from "react";
import { motion } from "framer-motion";
import { Video, Settings } from "lucide-react";
import { FileUploader } from "@/components/tools/FileUploader";
@@ -10,7 +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 { useTranslation, getServerTranslations } from "@/lib/i18n";
import type { UploadedFile, ProcessedFile, VideoFramesConfig } from "@/types";
const videoAccept = {
@@ -25,15 +25,13 @@ const defaultConfig: VideoFramesConfig = {
height: undefined,
};
function useConfigOptions(config: VideoFramesConfig): ConfigOption[] {
const { t } = useTranslation();
function useConfigOptions(config: VideoFramesConfig, getT: (key: string) => string): ConfigOption[] {
return [
{
id: "fps",
type: "slider",
label: t("config.videoFrames.fps"),
description: t("config.videoFrames.fpsDescription"),
label: getT("config.videoFrames.fps"),
description: getT("config.videoFrames.fpsDescription"),
value: config.fps,
min: 1,
max: 60,
@@ -44,8 +42,8 @@ function useConfigOptions(config: VideoFramesConfig): ConfigOption[] {
{
id: "format",
type: "select",
label: t("config.videoFrames.format"),
description: t("config.videoFrames.formatDescription"),
label: getT("config.videoFrames.format"),
description: getT("config.videoFrames.formatDescription"),
value: config.format,
options: [
{ label: "PNG", value: "png" },
@@ -56,8 +54,8 @@ function useConfigOptions(config: VideoFramesConfig): ConfigOption[] {
{
id: "quality",
type: "slider",
label: t("config.videoFrames.quality"),
description: t("config.videoFrames.qualityDescription"),
label: getT("config.videoFrames.quality"),
description: getT("config.videoFrames.qualityDescription"),
value: config.quality,
min: 1,
max: 100,
@@ -68,7 +66,15 @@ function useConfigOptions(config: VideoFramesConfig): ConfigOption[] {
}
export default function VideoFramesPage() {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);
const { t } = useTranslation();
const getT = (key: string) => {
if (!mounted) return getServerTranslations("en").t(key);
return t(key);
};
const { files, addFile, removeFile, clearFiles, processingStatus, setProcessingStatus } =
useUploadStore();
@@ -105,7 +111,7 @@ export default function VideoFramesPage() {
setProcessingStatus({
status: "uploading",
progress: 0,
message: t("processing.uploadingVideo"),
message: getT("processing.uploadingVideo"),
});
try {
@@ -115,14 +121,14 @@ export default function VideoFramesPage() {
setProcessingStatus({
status: "uploading",
progress: i,
message: t("processing.uploadProgress", { progress: i }),
message: getT("processing.uploadProgress", { progress: i }),
});
}
setProcessingStatus({
status: "processing",
progress: 0,
message: t("processing.extractingFrames"),
message: getT("processing.extractingFrames"),
});
// Simulate processing
@@ -131,7 +137,7 @@ export default function VideoFramesPage() {
setProcessingStatus({
status: "processing",
progress: i,
message: t("processing.processProgress", { progress: i }),
message: getT("processing.processProgress", { progress: i }),
});
}
@@ -155,14 +161,14 @@ export default function VideoFramesPage() {
setProcessingStatus({
status: "completed",
progress: 100,
message: t("processing.processingComplete"),
message: getT("processing.processingComplete"),
});
} catch (error) {
setProcessingStatus({
status: "failed",
progress: 0,
message: t("processing.processingFailed"),
error: error instanceof Error ? error.message : t("processing.unknownError"),
message: getT("processing.processingFailed"),
error: error instanceof Error ? error.message : getT("processing.unknownError"),
});
}
};
@@ -172,7 +178,7 @@ export default function VideoFramesPage() {
};
const canProcess = files.length > 0 && processingStatus.status !== "processing";
const configOptions = useConfigOptions(config);
const configOptions = useConfigOptions(config, getT);
return (
<div className="p-6">
@@ -187,9 +193,9 @@ export default function VideoFramesPage() {
<Video className="h-6 w-6 text-primary" />
</div>
<div>
<h1 className="text-3xl font-bold">{t("tools.videoFrames.title")}</h1>
<h1 className="text-3xl font-bold">{getT("tools.videoFrames.title")}</h1>
<p className="text-muted-foreground">
{t("tools.videoFrames.description")}
{getT("tools.videoFrames.description")}
</p>
</div>
</div>
@@ -209,8 +215,8 @@ export default function VideoFramesPage() {
/>
<ConfigPanel
title={t("config.videoFrames.title")}
description={t("config.videoFrames.description")}
title={getT("config.videoFrames.title")}
description={getT("config.videoFrames.description")}
options={configOptions.map((opt) => ({
...opt,
value: config[opt.id as keyof VideoFramesConfig],
@@ -222,7 +228,7 @@ export default function VideoFramesPage() {
{canProcess && (
<Button onClick={handleProcess} size="lg" className="w-full">
<Settings className="mr-2 h-4 w-4" />
{t("tools.videoFrames.processVideo")}
{getT("tools.videoFrames.processVideo")}
</Button>
)}
</div>
@@ -239,9 +245,9 @@ 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">{t("tools.videoFrames.howItWorks")}</h3>
<h3 className="mb-3 font-semibold">{getT("tools.videoFrames.howItWorks")}</h3>
<ol className="space-y-2 text-sm text-muted-foreground">
{(t("tools.videoFrames.steps") as unknown as string[]).map((step, index) => (
{(getT("tools.videoFrames.steps") as unknown as string[]).map((step, index) => (
<li key={index}>{index + 1}. {step}</li>
))}
</ol>