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

@@ -0,0 +1,39 @@
import type { Metadata } from "next";
import { headers } from "next/headers";
export async function generateMetadata(): Promise<Metadata> {
const headersList = await headers();
const acceptLanguage = headersList.get("accept-language") || "";
const lang = acceptLanguage.includes("zh") ? "zh" : "en";
const titles = {
en: "Image Compression - Optimize Images for Web & Mobile",
zh: "图片压缩 - 为网页和移动端优化图片",
};
const descriptions = {
en: "Optimize images for web and mobile without quality loss. Support for batch processing and format conversion including PNG, JPEG, WebP.",
zh: "为网页和移动端优化图片,不影响质量。支持批量处理和格式转换,包括 PNG、JPEG、WebP。",
};
return {
title: titles[lang],
description: descriptions[lang],
openGraph: {
title: titles[lang],
description: descriptions[lang],
},
twitter: {
title: titles[lang],
description: descriptions[lang],
},
};
}
export default function ImageCompressLayout({
children,
}: {
children: React.ReactNode;
}) {
return children;
}

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, ImageCompressConfig } from "@/types";
const imageAccept = {
@@ -21,35 +22,40 @@ const defaultConfig: ImageCompressConfig = {
format: "original",
};
const configOptions: ConfigOption[] = [
{
id: "quality",
type: "slider",
label: "Compression Quality",
description: "Lower quality = smaller file size",
value: defaultConfig.quality,
min: 1,
max: 100,
step: 1,
suffix: "%",
icon: <Zap className="h-4 w-4" />,
},
{
id: "format",
type: "select",
label: "Output Format",
description: "Convert to a different format (optional)",
value: defaultConfig.format,
options: [
{ label: "Original", value: "original" },
{ label: "JPEG", value: "jpeg" },
{ label: "PNG", value: "png" },
{ label: "WebP", value: "webp" },
],
},
];
function useConfigOptions(config: ImageCompressConfig): ConfigOption[] {
const { t } = useTranslation();
return [
{
id: "quality",
type: "slider",
label: t("config.imageCompression.quality"),
description: t("config.imageCompression.qualityDescription"),
value: config.quality,
min: 1,
max: 100,
step: 1,
suffix: "%",
icon: <Zap className="h-4 w-4" />,
},
{
id: "format",
type: "select",
label: t("config.imageCompression.format"),
description: t("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" },
],
},
];
}
export default function ImageCompressPage() {
const { t } = useTranslation();
const { files, addFile, removeFile, clearFiles, processingStatus, setProcessingStatus } =
useUploadStore();
@@ -86,7 +92,7 @@ export default function ImageCompressPage() {
setProcessingStatus({
status: "uploading",
progress: 0,
message: "Uploading images...",
message: t("processing.uploadingImages"),
});
try {
@@ -96,14 +102,14 @@ export default function ImageCompressPage() {
setProcessingStatus({
status: "uploading",
progress: i,
message: `Uploading... ${i}%`,
message: t("processing.uploadProgress", { progress: i }),
});
}
setProcessingStatus({
status: "processing",
progress: 0,
message: "Compressing images...",
message: t("processing.compressingImages"),
});
// Simulate processing
@@ -112,7 +118,7 @@ export default function ImageCompressPage() {
setProcessingStatus({
status: "processing",
progress: i,
message: `Compressing... ${i}%`,
message: t("processing.compressProgress", { progress: i }),
});
}
@@ -135,14 +141,14 @@ export default function ImageCompressPage() {
setProcessingStatus({
status: "completed",
progress: 100,
message: "Compression complete!",
message: t("processing.compressionComplete"),
});
} catch (error) {
setProcessingStatus({
status: "failed",
progress: 0,
message: "Compression failed",
error: error instanceof Error ? error.message : "Unknown error",
message: t("processing.compressionFailed"),
error: error instanceof Error ? error.message : t("processing.unknownError"),
});
}
};
@@ -152,6 +158,7 @@ export default function ImageCompressPage() {
};
const canProcess = files.length > 0 && processingStatus.status !== "processing";
const configOptions = useConfigOptions(config);
return (
<div className="p-6">
@@ -165,9 +172,9 @@ export default function ImageCompressPage() {
<ImageIcon className="h-6 w-6 text-primary" />
</div>
<div>
<h1 className="text-3xl font-bold">Image Compression</h1>
<h1 className="text-3xl font-bold">{t("tools.imageCompression.title")}</h1>
<p className="text-muted-foreground">
Optimize images for web and mobile without quality loss
{t("tools.imageCompression.description")}
</p>
</div>
</div>
@@ -186,8 +193,8 @@ export default function ImageCompressPage() {
/>
<ConfigPanel
title="Compression Settings"
description="Configure compression options"
title={t("config.imageCompression.title")}
description={t("config.imageCompression.description")}
options={configOptions.map((opt) => ({
...opt,
value: config[opt.id as keyof ImageCompressConfig],
@@ -199,7 +206,7 @@ export default function ImageCompressPage() {
{canProcess && (
<Button onClick={handleProcess} size="lg" className="w-full">
<Zap className="mr-2 h-4 w-4" />
Compress Images
{t("tools.imageCompression.compressImages")}
</Button>
)}
</div>
@@ -214,12 +221,11 @@ export default function ImageCompressPage() {
)}
<div className="rounded-lg border border-border/40 bg-card/50 p-6">
<h3 className="mb-3 font-semibold">Features</h3>
<h3 className="mb-3 font-semibold">{t("tools.imageCompression.features")}</h3>
<ul className="space-y-2 text-sm text-muted-foreground">
<li> Batch processing - compress multiple images at once</li>
<li> Smart compression - maintains visual quality</li>
<li> Format conversion - PNG to JPEG, WebP, and more</li>
<li> Up to 80% size reduction without quality loss</li>
{(t("tools.imageCompression.featureList") as unknown as string[]).map((feature, index) => (
<li key={index}> {feature}</li>
))}
</ul>
</div>
</div>