fix: 修复服务端渲染时的翻译不匹配问题
This commit is contained in:
164
src/app/page.tsx
164
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 (
|
||||
<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>
|
||||
|
||||
Reference in New Issue
Block a user