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

@@ -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 (
<section className="relative overflow-hidden">
{/* Background gradient */}
@@ -121,22 +153,22 @@ function HeroSection() {
>
<Badge className="mb-4" variant="secondary">
<Sparkles className="mr-1 h-3 w-3" />
{t("home.hero.badge")}
{getT("home.hero.badge")}
</Badge>
<h1 className="mb-6 text-4xl font-bold tracking-tight sm:text-5xl md:text-6xl lg:text-7xl xl:text-8xl 2xl:text-9xl">
{t("home.hero.title", { speed: t("home.hero.speed") })}
{getT("home.hero.title")}
</h1>
<p className="mb-8 text-lg text-muted-foreground md:text-xl xl:text-2xl 2xl:text-3xl">
{t("home.hero.description")}
{getT("home.hero.description")}
</p>
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row xl:gap-6">
<Button size="lg" asChild className="xl:text-lg xl:px-8 xl:py-6 2xl:text-xl 2xl:px-10 2xl:py-7">
<Link href="/tools">
{t("home.hero.startBuilding")} <ArrowRight className="ml-2 h-4 w-4" />
{getT("home.hero.startBuilding")} <ArrowRight className="ml-2 h-4 w-4" />
</Link>
</Button>
<Button size="lg" variant="outline" asChild className="xl:text-lg xl:px-8 xl:py-6 2xl:text-xl 2xl:px-10 2xl:py-7">
<Link href="/pricing">{t("common.viewPricing")}</Link>
<Link href="/pricing">{getT("common.viewPricing")}</Link>
</Button>
</div>
@@ -149,15 +181,15 @@ function HeroSection() {
>
<div>
<div className="text-3xl font-bold md:text-4xl xl:text-5xl 2xl:text-6xl">10K+</div>
<div className="text-sm text-muted-foreground md:text-base xl:text-lg 2xl:text-xl">{t("home.hero.stats.developers")}</div>
<div className="text-sm text-muted-foreground md:text-base xl:text-lg 2xl:text-xl">{getT("home.hero.stats.developers")}</div>
</div>
<div>
<div className="text-3xl font-bold md:text-4xl xl:text-5xl 2xl:text-6xl">1M+</div>
<div className="text-sm text-muted-foreground md:text-base xl:text-lg 2xl:text-xl">{t("home.hero.stats.filesProcessed")}</div>
<div className="text-sm text-muted-foreground md:text-base xl:text-lg 2xl:text-xl">{getT("home.hero.stats.filesProcessed")}</div>
</div>
<div>
<div className="text-3xl font-bold md:text-4xl xl:text-5xl 2xl:text-6xl">99.9%</div>
<div className="text-sm text-muted-foreground md:text-base xl:text-lg 2xl:text-xl">{t("home.hero.stats.uptime")}</div>
<div className="text-sm text-muted-foreground md:text-base xl:text-lg 2xl:text-xl">{getT("home.hero.stats.uptime")}</div>
</div>
</motion.div>
</motion.div>
@@ -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 (
<section className="border-t border-border/40 bg-background/50 py-24 xl:py-32 2xl:py-40">
<div className="container">
@@ -181,10 +220,10 @@ function FeaturesSection() {
className="mb-16 text-center xl:mb-20 2xl:mb-24"
>
<h2 className="mb-4 text-3xl font-bold tracking-tight md:text-4xl xl:text-5xl 2xl:text-6xl">
{t("home.featuresSection.title")}
{getT("home.featuresSection.title")}
</h2>
<p className="text-lg text-muted-foreground md:text-xl xl:text-2xl 2xl:text-3xl">
{t("home.featuresSection.description")}
{getT("home.featuresSection.description")}
</p>
</motion.div>
@@ -208,7 +247,7 @@ function FeaturesSection() {
</CardHeader>
<CardContent>
<Button variant="ghost" size="sm" className="w-full xl:text-base 2xl:text-lg">
{t("common.tryNow")} <ArrowRight className="ml-2 h-4 w-4" />
{getT("common.tryNow")} <ArrowRight className="ml-2 h-4 w-4" />
</Button>
</CardContent>
</Card>
@@ -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 (
<section className="py-24 xl:py-32 2xl:py-40">
<div className="container">
@@ -236,13 +282,13 @@ function BenefitsSection() {
transition={{ duration: 0.5 }}
>
<h2 className="mb-4 text-3xl font-bold tracking-tight md:text-4xl xl:text-5xl 2xl:text-6xl">
{t("home.benefits.title")}
{getT("home.benefits.title")}
</h2>
<p className="mb-8 text-lg text-muted-foreground md:text-xl xl:text-2xl 2xl:text-3xl">
{t("home.benefits.description")}
{getT("home.benefits.description")}
</p>
<Button size="lg" asChild className="xl:text-lg xl:px-8 xl:py-6 2xl:text-xl 2xl:px-10 2xl:py-7">
<Link href="/about">{t("common.learnMore")}</Link>
<Link href="/about">{getT("common.learnMore")}</Link>
</Button>
</motion.div>
@@ -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 (
<section className="border-t border-border/40 bg-background/50 py-24 xl:py-32 2xl:py-40">
<div className="container">
@@ -290,10 +343,10 @@ function PricingSection() {
className="mb-16 text-center xl:mb-20 2xl:mb-24"
>
<h2 className="mb-4 text-3xl font-bold tracking-tight md:text-4xl xl:text-5xl 2xl:text-6xl">
{t("home.pricing.title")}
{getT("home.pricing.title")}
</h2>
<p className="text-lg text-muted-foreground md:text-xl xl:text-2xl 2xl:text-3xl">
{t("home.pricing.description")}
{getT("home.pricing.description")}
</p>
</motion.div>
@@ -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 (
<section className="py-24 xl:py-32 2xl:py-40">
<div className="container">
@@ -359,17 +419,17 @@ function CTASection() {
>
<div className="relative z-10">
<h2 className="mb-4 text-3xl font-bold tracking-tight md:text-4xl xl:text-5xl 2xl:text-6xl">
{t("home.cta.title")}
{getT("home.cta.title")}
</h2>
<p className="mb-8 text-lg text-muted-foreground md:text-xl xl:text-2xl 2xl:text-3xl">
{t("home.cta.description")}
{getT("home.cta.description")}
</p>
<div className="flex flex-col items-center justify-center gap-4 sm:flex-row xl:gap-6">
<Button size="lg" asChild className="xl:text-lg xl:px-8 xl:py-6 2xl:text-xl 2xl:px-10 2xl:py-7">
<Link href="/register">{t("home.cta.getStarted")}</Link>
<Link href="/register">{getT("home.cta.getStarted")}</Link>
</Button>
<Button size="lg" variant="outline" asChild className="xl:text-lg xl:px-8 xl:py-6 2xl:text-xl 2xl:px-10 2xl:py-7">
<Link href="/contact">{t("common.contactSales")}</Link>
<Link href="/contact">{getT("common.contactSales")}</Link>
</Button>
</div>
</div>