From b7402edf6a21991b1707fda5c2af065e111a0f7b Mon Sep 17 00:00:00 2001 From: richarjiang Date: Sat, 24 Jan 2026 16:38:47 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=9C=8D=E5=8A=A1?= =?UTF-8?q?=E7=AB=AF=E6=B8=B2=E6=9F=93=E6=97=B6=E7=9A=84=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E4=B8=8D=E5=8C=B9=E9=85=8D=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../(dashboard)/tools/audio-compress/page.tsx | 72 ++++---- .../(dashboard)/tools/image-compress/page.tsx | 62 ++++--- .../(dashboard)/tools/video-frames/page.tsx | 58 ++++--- src/app/page.tsx | 164 ++++++++++++------ src/components/layout/Footer.tsx | 39 +++-- src/components/layout/Header.tsx | 58 +++++-- src/components/layout/LanguageSwitcher.tsx | 6 +- src/components/layout/Sidebar.tsx | 33 ++-- src/lib/i18n.ts | 9 +- src/locales/en.json | 1 + src/locales/zh.json | 1 + 11 files changed, 319 insertions(+), 184 deletions(-) diff --git a/src/app/(dashboard)/tools/audio-compress/page.tsx b/src/app/(dashboard)/tools/audio-compress/page.tsx index d6c13c7..1f0d5c4 100644 --- a/src/app/(dashboard)/tools/audio-compress/page.tsx +++ b/src/app/(dashboard)/tools/audio-compress/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; import { motion } from "framer-motion"; import { Music, Volume2 } 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, AudioCompressConfig } from "@/types"; const audioAccept = { @@ -24,15 +24,13 @@ const defaultConfig: AudioCompressConfig = { channels: 2, }; -function useConfigOptions(config: AudioCompressConfig): ConfigOption[] { - const { t } = useTranslation(); - +function useConfigOptions(config: AudioCompressConfig, getT: (key: string) => string): ConfigOption[] { return [ { id: "bitrate", type: "select", - label: t("config.audioCompression.bitrate"), - description: t("config.audioCompression.bitrateDescription"), + label: getT("config.audioCompression.bitrate"), + description: getT("config.audioCompression.bitrateDescription"), value: config.bitrate, options: [ { label: "64 kbps", value: 64 }, @@ -45,8 +43,8 @@ function useConfigOptions(config: AudioCompressConfig): ConfigOption[] { { id: "format", type: "select", - label: t("config.audioCompression.format"), - description: t("config.audioCompression.formatDescription"), + label: getT("config.audioCompression.format"), + description: getT("config.audioCompression.formatDescription"), value: config.format, options: [ { label: "MP3", value: "mp3" }, @@ -58,8 +56,8 @@ function useConfigOptions(config: AudioCompressConfig): ConfigOption[] { { id: "sampleRate", type: "select", - label: t("config.audioCompression.sampleRate"), - description: t("config.audioCompression.sampleRateDescription"), + label: getT("config.audioCompression.sampleRate"), + description: getT("config.audioCompression.sampleRateDescription"), value: config.sampleRate, options: [ { label: "44.1 kHz", value: 44100 }, @@ -69,19 +67,27 @@ function useConfigOptions(config: AudioCompressConfig): ConfigOption[] { { id: "channels", type: "radio", - label: t("config.audioCompression.channels"), - description: t("config.audioCompression.channelsDescription"), + label: getT("config.audioCompression.channels"), + description: getT("config.audioCompression.channelsDescription"), value: config.channels, options: [ - { label: t("config.audioCompression.stereo"), value: 2 }, - { label: t("config.audioCompression.mono"), value: 1 }, + { label: getT("config.audioCompression.stereo"), value: 2 }, + { label: getT("config.audioCompression.mono"), value: 1 }, ], }, ]; } export default function AudioCompressPage() { + 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(); @@ -118,7 +124,7 @@ export default function AudioCompressPage() { setProcessingStatus({ status: "uploading", progress: 0, - message: t("processing.uploadingAudio"), + message: getT("processing.uploadingAudio"), }); try { @@ -128,14 +134,14 @@ export default function AudioCompressPage() { 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.compressingAudio"), + message: getT("processing.compressingAudio"), }); // Simulate processing @@ -144,7 +150,7 @@ export default function AudioCompressPage() { setProcessingStatus({ status: "processing", progress: i, - message: t("processing.compressProgress", { progress: i }), + message: getT("processing.compressProgress", { progress: i }), }); } @@ -168,14 +174,14 @@ export default function AudioCompressPage() { setProcessingStatus({ status: "completed", progress: 100, - message: t("processing.compressionComplete"), + message: getT("processing.compressionComplete"), }); } catch (error) { setProcessingStatus({ status: "failed", progress: 0, - message: t("processing.compressionFailed"), - error: error instanceof Error ? error.message : t("processing.unknownError"), + message: getT("processing.compressionFailed"), + error: error instanceof Error ? error.message : getT("processing.unknownError"), }); } }; @@ -185,7 +191,7 @@ export default function AudioCompressPage() { }; const canProcess = files.length > 0 && processingStatus.status !== "processing"; - const configOptions = useConfigOptions(config); + const configOptions = useConfigOptions(config, getT); return (
@@ -199,9 +205,9 @@ export default function AudioCompressPage() {
-

{t("tools.audioCompression.title")}

+

{getT("tools.audioCompression.title")}

- {t("tools.audioCompression.description")} + {getT("tools.audioCompression.description")}

@@ -220,8 +226,8 @@ export default function AudioCompressPage() { /> ({ ...opt, value: config[opt.id as keyof AudioCompressConfig], @@ -233,7 +239,7 @@ export default function AudioCompressPage() { {canProcess && ( )} @@ -248,15 +254,15 @@ export default function AudioCompressPage() { )}
-

{t("tools.audioCompression.supportedFormats")}

+

{getT("tools.audioCompression.supportedFormats")}

-

{t("tools.audioCompression.input")}

-

{t("tools.audioCompression.inputFormats")}

+

{getT("tools.audioCompression.input")}

+

{getT("tools.audioCompression.inputFormats")}

-

{t("tools.audioCompression.output")}

-

{t("tools.audioCompression.outputFormats")}

+

{getT("tools.audioCompression.output")}

+

{getT("tools.audioCompression.outputFormats")}

diff --git a/src/app/(dashboard)/tools/image-compress/page.tsx b/src/app/(dashboard)/tools/image-compress/page.tsx index 4232fd6..206c6a7 100644 --- a/src/app/(dashboard)/tools/image-compress/page.tsx +++ b/src/app/(dashboard)/tools/image-compress/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState, useCallback } from "react"; +import { useState, useCallback, useEffect } from "react"; import { motion } from "framer-motion"; import { Image as ImageIcon, Zap } 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, ImageCompressConfig } from "@/types"; const imageAccept = { @@ -22,15 +22,13 @@ const defaultConfig: ImageCompressConfig = { format: "original", }; -function useConfigOptions(config: ImageCompressConfig): ConfigOption[] { - const { t } = useTranslation(); - +function useConfigOptions(config: ImageCompressConfig, getT: (key: string) => string): ConfigOption[] { return [ { id: "quality", type: "slider", - label: t("config.imageCompression.quality"), - description: t("config.imageCompression.qualityDescription"), + label: getT("config.imageCompression.quality"), + description: getT("config.imageCompression.qualityDescription"), value: config.quality, min: 1, max: 100, @@ -41,21 +39,29 @@ function useConfigOptions(config: ImageCompressConfig): ConfigOption[] { { id: "format", type: "select", - label: t("config.imageCompression.format"), - description: t("config.imageCompression.formatDescription"), + label: getT("config.imageCompression.format"), + description: getT("config.imageCompression.formatDescription"), value: config.format, options: [ - { label: t("config.imageCompression.formatOriginal"), value: "original" }, - { label: t("config.imageCompression.formatJpeg"), value: "jpeg" }, - { label: t("config.imageCompression.formatPng"), value: "png" }, - { label: t("config.imageCompression.formatWebp"), value: "webp" }, + { label: getT("config.imageCompression.formatOriginal"), value: "original" }, + { label: getT("config.imageCompression.formatJpeg"), value: "jpeg" }, + { label: getT("config.imageCompression.formatPng"), value: "png" }, + { label: getT("config.imageCompression.formatWebp"), value: "webp" }, ], }, ]; } export default function ImageCompressPage() { + 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(); @@ -92,7 +98,7 @@ export default function ImageCompressPage() { setProcessingStatus({ status: "uploading", progress: 0, - message: t("processing.uploadingImages"), + message: getT("processing.uploadingImages"), }); try { @@ -102,14 +108,14 @@ export default function ImageCompressPage() { 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.compressingImages"), + message: getT("processing.compressingImages"), }); // Simulate processing @@ -118,7 +124,7 @@ export default function ImageCompressPage() { setProcessingStatus({ status: "processing", progress: i, - message: t("processing.compressProgress", { progress: i }), + message: getT("processing.compressProgress", { progress: i }), }); } @@ -141,14 +147,14 @@ export default function ImageCompressPage() { setProcessingStatus({ status: "completed", progress: 100, - message: t("processing.compressionComplete"), + message: getT("processing.compressionComplete"), }); } catch (error) { setProcessingStatus({ status: "failed", progress: 0, - message: t("processing.compressionFailed"), - error: error instanceof Error ? error.message : t("processing.unknownError"), + message: getT("processing.compressionFailed"), + error: error instanceof Error ? error.message : getT("processing.unknownError"), }); } }; @@ -158,7 +164,7 @@ export default function ImageCompressPage() { }; const canProcess = files.length > 0 && processingStatus.status !== "processing"; - const configOptions = useConfigOptions(config); + const configOptions = useConfigOptions(config, getT); return (
@@ -172,9 +178,9 @@ export default function ImageCompressPage() {
-

{t("tools.imageCompression.title")}

+

{getT("tools.imageCompression.title")}

- {t("tools.imageCompression.description")} + {getT("tools.imageCompression.description")}

@@ -193,8 +199,8 @@ export default function ImageCompressPage() { /> ({ ...opt, value: config[opt.id as keyof ImageCompressConfig], @@ -206,7 +212,7 @@ export default function ImageCompressPage() { {canProcess && ( )} @@ -221,9 +227,9 @@ export default function ImageCompressPage() { )}
-

{t("tools.imageCompression.features")}

+

{getT("tools.imageCompression.features")}

    - {(t("tools.imageCompression.featureList") as unknown as string[]).map((feature, index) => ( + {(getT("tools.imageCompression.featureList") as unknown as string[]).map((feature, index) => (
  • • {feature}
  • ))}
diff --git a/src/app/(dashboard)/tools/video-frames/page.tsx b/src/app/(dashboard)/tools/video-frames/page.tsx index a9c6361..cc58728 100644 --- a/src/app/(dashboard)/tools/video-frames/page.tsx +++ b/src/app/(dashboard)/tools/video-frames/page.tsx @@ -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 (
@@ -187,9 +193,9 @@ export default function VideoFramesPage() {
-

{t("tools.videoFrames.title")}

+

{getT("tools.videoFrames.title")}

- {t("tools.videoFrames.description")} + {getT("tools.videoFrames.description")}

@@ -209,8 +215,8 @@ export default function VideoFramesPage() { /> ({ ...opt, value: config[opt.id as keyof VideoFramesConfig], @@ -222,7 +228,7 @@ export default function VideoFramesPage() { {canProcess && ( )} @@ -239,9 +245,9 @@ export default function VideoFramesPage() { {/* Info Card */}
-

{t("tools.videoFrames.howItWorks")}

+

{getT("tools.videoFrames.howItWorks")}

    - {(t("tools.videoFrames.steps") as unknown as string[]).map((step, index) => ( + {(getT("tools.videoFrames.steps") as unknown as string[]).map((step, index) => (
  1. {index + 1}. {step}
  2. ))}
diff --git a/src/app/page.tsx b/src/app/page.tsx index b1ea52d..227686e 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -16,94 +16,126 @@ import { import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; -import { useTranslation } from "@/lib/i18n"; +import { useTranslation, getServerTranslations } from "@/lib/i18n"; +import { useState, useEffect } from "react"; function useFeatures() { + 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); + }; + return [ { icon: Video, - title: t("home.tools.videoToFrames.title"), - description: t("home.tools.videoToFrames.description"), + title: getT("home.tools.videoToFrames.title"), + description: getT("home.tools.videoToFrames.description"), href: "/tools/video-frames", }, { icon: Image, - title: t("home.tools.imageCompression.title"), - description: t("home.tools.imageCompression.description"), + title: getT("home.tools.imageCompression.title"), + description: getT("home.tools.imageCompression.description"), href: "/tools/image-compress", }, { icon: Music, - title: t("home.tools.audioCompression.title"), - description: t("home.tools.audioCompression.description"), + title: getT("home.tools.audioCompression.title"), + description: getT("home.tools.audioCompression.description"), href: "/tools/audio-compress", }, { icon: Sparkles, - title: t("home.tools.aiTools.title"), - description: t("home.tools.aiTools.description"), + title: getT("home.tools.aiTools.title"), + description: getT("home.tools.aiTools.description"), href: "/tools/ai-tools", }, ]; } function useBenefits() { + 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); + }; + return [ { icon: Zap, - title: t("home.benefits.lightningFast.title"), - description: t("home.benefits.lightningFast.description"), + title: getT("home.benefits.lightningFast.title"), + description: getT("home.benefits.lightningFast.description"), }, { icon: Shield, - title: t("home.benefits.secure.title"), - description: t("home.benefits.secure.description"), + title: getT("home.benefits.secure.title"), + description: getT("home.benefits.secure.description"), }, { icon: Users, - title: t("home.benefits.forDevelopers.title"), - description: t("home.benefits.forDevelopers.description"), + title: getT("home.benefits.forDevelopers.title"), + description: getT("home.benefits.forDevelopers.description"), }, ]; } function usePricingPlans() { + 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); + }; + return [ { - name: t("home.pricing.plans.free.name"), - price: t("home.pricing.plans.free.price"), - description: t("home.pricing.plans.free.description"), - features: t("home.pricing.plans.free.features") as unknown as string[], - cta: t("home.pricing.plans.free.cta"), + name: getT("home.pricing.plans.free.name"), + price: getT("home.pricing.plans.free.price"), + description: getT("home.pricing.plans.free.description"), + features: (mounted ? t("home.pricing.plans.free.features") : getServerTranslations("en").t("home.pricing.plans.free.features")) as unknown as string[], + cta: getT("home.pricing.plans.free.cta"), href: "/register", }, { - name: t("home.pricing.plans.pro.name"), - price: t("home.pricing.plans.pro.price"), - period: t("home.pricing.plans.pro.period"), - description: t("home.pricing.plans.pro.description"), - features: t("home.pricing.plans.pro.features") as unknown as string[], - cta: t("home.pricing.plans.pro.cta"), + name: getT("home.pricing.plans.pro.name"), + price: getT("home.pricing.plans.pro.price"), + period: getT("home.pricing.plans.pro.period"), + description: getT("home.pricing.plans.pro.description"), + features: (mounted ? t("home.pricing.plans.pro.features") : getServerTranslations("en").t("home.pricing.plans.pro.features")) as unknown as string[], + cta: getT("home.pricing.plans.pro.cta"), href: "/pricing", - popular: t("home.pricing.plans.pro.popular") as string, + popular: getT("home.pricing.plans.pro.popular"), }, { - name: t("home.pricing.plans.enterprise.name"), - price: t("home.pricing.plans.enterprise.price"), - description: t("home.pricing.plans.enterprise.description"), - features: t("home.pricing.plans.enterprise.features") as unknown as string[], - cta: t("home.pricing.plans.enterprise.cta"), + name: getT("home.pricing.plans.enterprise.name"), + price: getT("home.pricing.plans.enterprise.price"), + description: getT("home.pricing.plans.enterprise.description"), + features: (mounted ? t("home.pricing.plans.enterprise.features") : getServerTranslations("en").t("home.pricing.plans.enterprise.features")) as unknown as string[], + cta: getT("home.pricing.plans.enterprise.cta"), href: "/contact", }, ]; } function HeroSection() { + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); const { t } = useTranslation(); + const getT = (key: string, params?: any) => { + if (!mounted) return getServerTranslations("en").t(key, params); + return t(key, params); + }; + return (
{/* Background gradient */} @@ -121,22 +153,22 @@ function HeroSection() { > - {t("home.hero.badge")} + {getT("home.hero.badge")}

- {t("home.hero.title", { speed: t("home.hero.speed") })} + {getT("home.hero.title")}

- {t("home.hero.description")} + {getT("home.hero.description")}

@@ -149,15 +181,15 @@ function HeroSection() { >
10K+
-
{t("home.hero.stats.developers")}
+
{getT("home.hero.stats.developers")}
1M+
-
{t("home.hero.stats.filesProcessed")}
+
{getT("home.hero.stats.filesProcessed")}
99.9%
-
{t("home.hero.stats.uptime")}
+
{getT("home.hero.stats.uptime")}
@@ -167,9 +199,16 @@ function HeroSection() { } function FeaturesSection() { + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); const { t } = useTranslation(); const features = useFeatures(); + const getT = (key: string) => { + if (!mounted) return getServerTranslations("en").t(key); + return t(key); + }; + return (
@@ -181,10 +220,10 @@ function FeaturesSection() { className="mb-16 text-center xl:mb-20 2xl:mb-24" >

- {t("home.featuresSection.title")} + {getT("home.featuresSection.title")}

- {t("home.featuresSection.description")} + {getT("home.featuresSection.description")}

@@ -208,7 +247,7 @@ function FeaturesSection() { @@ -222,9 +261,16 @@ function FeaturesSection() { } function BenefitsSection() { + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); const { t } = useTranslation(); const benefits = useBenefits(); + const getT = (key: string) => { + if (!mounted) return getServerTranslations("en").t(key); + return t(key); + }; + return (
@@ -236,13 +282,13 @@ function BenefitsSection() { transition={{ duration: 0.5 }} >

- {t("home.benefits.title")} + {getT("home.benefits.title")}

- {t("home.benefits.description")} + {getT("home.benefits.description")}

@@ -276,9 +322,16 @@ function BenefitsSection() { } function PricingSection() { + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); const { t } = useTranslation(); const pricingPlans = usePricingPlans(); + const getT = (key: string) => { + if (!mounted) return getServerTranslations("en").t(key); + return t(key); + }; + return (
@@ -290,10 +343,10 @@ function PricingSection() { className="mb-16 text-center xl:mb-20 2xl:mb-24" >

- {t("home.pricing.title")} + {getT("home.pricing.title")}

- {t("home.pricing.description")} + {getT("home.pricing.description")}

@@ -345,8 +398,15 @@ function PricingSection() { } function CTASection() { + 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); + }; + return (
@@ -359,17 +419,17 @@ function CTASection() { >

- {t("home.cta.title")} + {getT("home.cta.title")}

- {t("home.cta.description")} + {getT("home.cta.description")}

diff --git a/src/components/layout/Footer.tsx b/src/components/layout/Footer.tsx index 6c36cdd..9b29c90 100644 --- a/src/components/layout/Footer.tsx +++ b/src/components/layout/Footer.tsx @@ -2,26 +2,34 @@ import Link from "next/link"; import { Sparkles, Github, Twitter } from "lucide-react"; -import { useTranslation } from "@/lib/i18n"; +import { useTranslation, getServerTranslations } from "@/lib/i18n"; +import { useState, useEffect } from "react"; function useFooterLinks() { + 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); + }; + return { product: [ - { name: t("common.features"), href: "/features" }, - { name: t("nav.pricing"), href: "/pricing" }, + { name: getT("common.features"), href: "/features" }, + { name: getT("nav.pricing"), href: "/pricing" }, { name: "API", href: "/api" }, - { name: t("nav.docs"), href: "/docs" }, + { name: getT("nav.docs"), href: "/docs" }, ], tools: [ - { name: t("sidebar.videoToFrames"), href: "/tools/video-frames" }, - { name: t("sidebar.imageCompression"), href: "/tools/image-compress" }, - { name: t("sidebar.audioCompression"), href: "/tools/audio-compress" }, - { name: t("home.tools.aiTools.title"), href: "/tools/ai-tools" }, + { name: getT("sidebar.videoToFrames"), href: "/tools/video-frames" }, + { name: getT("sidebar.imageCompression"), href: "/tools/image-compress" }, + { name: getT("sidebar.audioCompression"), href: "/tools/audio-compress" }, + { name: getT("home.tools.aiTools.title"), href: "/tools/ai-tools" }, ], company: [ - { name: t("nav.about"), href: "/about" }, + { name: getT("nav.about"), href: "/about" }, { name: "Blog", href: "/blog" }, { name: "Careers", href: "/careers" }, { name: "Contact", href: "/contact" }, @@ -47,9 +55,16 @@ const sectionTitles: Record = { }; export function Footer() { + const [mounted, setMounted] = useState(false); + useEffect(() => setMounted(true), []); const { t } = useTranslation(); const footerLinks = useFooterLinks(); + const getT = (key: string) => { + if (!mounted) return getServerTranslations("en").t(key); + return t(key); + }; + return (