Reverted all changes back to version 1d41f871ee

This commit is contained in:
[dyad]
2026-01-18 12:44:57 +01:00
parent 7dd33d2854
commit 256c071bec
11 changed files with 118 additions and 799 deletions

View File

@@ -1,44 +0,0 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { Toaster } from "@/components/ui/sonner";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Image Web Exporter",
description: "Upload a picture, then export it in a different resolution and format.",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
<Toaster />
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,17 +1,44 @@
import { ReactNode } from 'react';
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/theme-provider";
import { Toaster } from "@/components/ui/sonner";
type Props = {
children: ReactNode;
params: { locale: string };
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Image Web Exporter",
description: "Upload a picture, then export it in a different resolution and format.",
};
// Even though this component is just passing its children through, the presence
// of this file fixes an issue in Next.js 13.4 where link clicks that switch
// the locale would otherwise cause a full page reload.
export default function RootLayout({ children, params: { locale } }: Props) {
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang={locale}>
<body>{children}</body>
<html lang="en" suppressHydrationWarning>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
{children}
<Toaster />
</ThemeProvider>
</body>
</html>
);
}

View File

@@ -1,22 +1,15 @@
import { ImageConverter } from "@/components/image-converter";
import { LanguageSwitcher } from "@/components/language-switcher";
import { useTranslations } from "next-intl";
export default function Home() {
const t = useTranslations("HomePage");
return (
<div className="relative flex flex-col items-center justify-center min-h-screen p-4 sm:p-8 bg-gray-50 dark:bg-background font-[family-name:var(--font-geist-sans)]">
<div className="absolute top-4 right-4 z-20">
<LanguageSwitcher />
</div>
<main className="flex flex-col items-center w-full max-w-6xl z-10">
<div className="text-center mb-8">
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-5xl">
{t("title")}
Image Web Exporter
</h1>
<p className="mt-3 text-lg text-gray-600 dark:text-gray-400">
{t("description")}
Upload a picture, then export it in a different resolution and format.
</p>
</div>
<ImageConverter />

View File

@@ -36,18 +36,16 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useTranslations } from "next-intl";
const aspectRatios = [
{ name: "custom", value: "custom" },
{ name: "square", value: "1/1" },
{ name: "standard", value: "4/3" },
{ name: "photography", value: "3/2" },
{ name: "widescreen", value: "16/9" },
{ name: "Custom", value: "custom" },
{ name: "1:1 (Square)", value: "1/1" },
{ name: "4:3 (Standard)", value: "4/3" },
{ name: "3:2 (Photography)", value: "3/2" },
{ name: "16:9 (Widescreen)", value: "16/9" },
];
export function ImageConverter() {
const t = useTranslations("ImageConverter");
const [images, setImages] = useState<File[]>([]);
const [previewUrls, setPreviewUrls] = useState<string[]>([]);
const [filenames, setFilenames] = useState<string[]>([]);
@@ -88,7 +86,7 @@ export function ImageConverter() {
);
if (imageFiles.length === 0) {
toast.error(t("toasts.noValidImages"));
toast.error("No valid image files found.");
return;
}
@@ -110,7 +108,7 @@ export function ImageConverter() {
setPreviewUrls(newPreviewUrls);
setFilenames(newFilenames);
toast.success(t("toasts.imagesAdded", { count: imageFiles.length }));
toast.success(`${imageFiles.length} image(s) added.`);
};
const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
@@ -150,7 +148,7 @@ export function ImageConverter() {
setImages([]);
setPreviewUrls([]);
setFilenames([]);
toast.info(t("toasts.allCleared"));
toast.info("All images cleared.");
};
const handleFilenameChange = (index: number, newName: string) => {
@@ -262,12 +260,12 @@ export function ImageConverter() {
const handleConvertAndDownloadAll = async () => {
if (images.length === 0) {
toast.error(t("toasts.noImages"));
toast.error("Please upload images first.");
return;
}
setIsConverting(true);
toast.info(t("toasts.conversionStarting", { count: images.length }));
toast.info(`Starting conversion for ${images.length} images...`);
const conversionPromises = images.map((image, index) =>
convertAndDownload(image, previewUrls[index], index)
@@ -275,12 +273,12 @@ export function ImageConverter() {
try {
await Promise.all(conversionPromises);
toast.success(t("toasts.conversionSuccess", { count: images.length }));
toast.success(`Successfully exported all ${images.length} images!`);
} catch (error) {
if (error instanceof Error) {
toast.error(error.message);
} else {
toast.error(t("toasts.conversionError"));
toast.error("An unknown error occurred during conversion.");
}
} finally {
setIsConverting(false);
@@ -289,16 +287,16 @@ export function ImageConverter() {
const handleConvertAndDownloadSingle = async (index: number) => {
setConvertingIndex(index);
toast.info(t("toasts.singleConversionStarting", { filename: filenames[index] }));
toast.info(`Starting conversion for ${filenames[index]}...`);
try {
await convertAndDownload(images[index], previewUrls[index], index);
toast.success(t("toasts.singleConversionSuccess", { filename: filenames[index] }));
toast.success(`Successfully exported ${filenames[index]}!`);
} catch (error) {
if (error instanceof Error) {
toast.error(error.message);
} else {
toast.error(t("toasts.conversionError"));
toast.error("An unknown error occurred during conversion.");
}
} finally {
setConvertingIndex(null);
@@ -306,7 +304,7 @@ export function ImageConverter() {
};
const handleApplySettings = () => {
toast.info(t("toasts.settingsApplied"));
toast.info("Settings updated and will be used for all downloads.");
};
const handleAspectRatioChange = (value: string) => {
@@ -354,16 +352,16 @@ export function ImageConverter() {
const handleApplyDefaultBaseNameToAll = () => {
if (!defaultBaseName) {
toast.error(t("toasts.noDefaultBaseName"));
toast.error("Please enter a default base name to apply.");
return;
}
if (!hasImages) {
toast.info(t("toasts.uploadFirst"));
toast.info("Upload some images first.");
return;
}
const newFilenames = filenames.map(() => defaultBaseName);
setFilenames(newFilenames);
toast.success(t("toasts.baseNameApplied", { baseName: defaultBaseName, count: images.length }));
toast.success(`Set base name to "${defaultBaseName}" for all ${images.length} images.`);
};
return (
@@ -374,9 +372,9 @@ export function ImageConverter() {
<AccordionItem value="image-settings" className="border rounded-lg bg-card">
<AccordionTrigger className="p-6 hover:no-underline">
<div className="text-left">
<h3 className="text-lg font-medium leading-none">{t("settings.image.title")}</h3>
<h3 className="text-lg font-medium leading-none">Image Settings</h3>
<p className="text-sm text-muted-foreground mt-1">
{t("settings.image.description")}
Adjust resolution and scaling for all images.
</p>
</div>
</AccordionTrigger>
@@ -384,13 +382,13 @@ export function ImageConverter() {
<div className="space-y-4">
<div>
<div className="flex items-center gap-1.5">
<Label htmlFor="aspect-ratio">{t("settings.image.aspectRatio")}</Label>
<Label htmlFor="aspect-ratio">Aspect Ratio</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.image.aspectRatioTooltip")}</p>
<p>Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -401,7 +399,7 @@ export function ImageConverter() {
<SelectContent>
{aspectRatios.map((ratio) => (
<SelectItem key={ratio.value} value={ratio.value}>
{t(`settings.image.aspectRatios.${ratio.name}`)}
{ratio.name}
</SelectItem>
))}
</SelectContent>
@@ -410,13 +408,13 @@ export function ImageConverter() {
<div className="flex items-end gap-2">
<div className="space-y-2 flex-1">
<div className="flex items-center gap-1.5">
<Label htmlFor="width">{t("settings.image.width")}</Label>
<Label htmlFor="width">Width (px)</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.image.widthTooltip")}</p>
<p>Set the output width in pixels. Leave blank to use the original width.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -429,18 +427,18 @@ export function ImageConverter() {
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.image.swapDimensionsTooltip")}</p>
<p>Swap the entered width and height values.</p>
</TooltipContent>
</Tooltip>
<div className="space-y-2 flex-1">
<div className="flex items-center gap-1.5">
<Label htmlFor="height">{t("settings.image.height")}</Label>
<Label htmlFor="height">Height (px)</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.image.heightTooltip")}</p>
<p>Set the output height in pixels. Leave blank to use the original height.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -450,13 +448,13 @@ export function ImageConverter() {
<div className="flex items-center space-x-2 pt-2">
<Checkbox id="keep-orientation" checked={keepOrientation} onCheckedChange={(checked) => setKeepOrientation(Boolean(checked))} />
<Label htmlFor="keep-orientation" className="cursor-pointer flex items-center gap-1.5">
{t("settings.image.keepOrientation")}
Keep original orientation
<Tooltip>
<TooltipTrigger onClick={(e) => e.preventDefault()}>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.image.keepOrientationTooltip")}</p>
<p>Automatically swaps width and height to match the original image's orientation.</p>
</TooltipContent>
</Tooltip>
</Label>
@@ -464,35 +462,35 @@ export function ImageConverter() {
</div>
<div className="mt-4 space-y-2">
<div className="flex items-center gap-1.5">
<Label htmlFor="scale-mode">{t("settings.image.scaling")}</Label>
<Label htmlFor="scale-mode">Scaling</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.image.scalingTooltip")}</p>
<p>Determines how the image fits into the new dimensions.</p>
</TooltipContent>
</Tooltip>
</div>
<Select value={scaleMode} onValueChange={(value: 'fill' | 'cover' | 'contain') => setScaleMode(value)}>
<SelectTrigger id="scale-mode"><SelectValue placeholder="Select scaling mode" /></SelectTrigger>
<SelectContent>
<SelectItem value="fill">{t("settings.image.scalingOptions.fill")}</SelectItem>
<SelectItem value="cover">{t("settings.image.scalingOptions.cover")}</SelectItem>
<SelectItem value="contain">{t("settings.image.scalingOptions.contain")}</SelectItem>
<SelectItem value="fill">Fill (stretch to fit)</SelectItem>
<SelectItem value="cover">Cover (crop to fit)</SelectItem>
<SelectItem value="contain">Contain (letterbox)</SelectItem>
</SelectContent>
</Select>
</div>
{scaleMode !== 'fill' && (
<div className="mt-4 space-y-2">
<div className="flex items-center gap-1.5">
<Label>{t("settings.image.position")}</Label>
<Label>Position</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.image.positionTooltip")}</p>
<p>Sets the anchor point for 'Cover' or 'Contain' scaling.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -505,8 +503,8 @@ export function ImageConverter() {
<AccordionItem value="filename-settings" className="border rounded-lg bg-card">
<AccordionTrigger className="p-6 hover:no-underline">
<div className="text-left">
<h3 className="text-lg font-medium leading-none">{t("settings.filename.title")}</h3>
<p className="text-sm text-muted-foreground mt-1">{t("settings.filename.description")}</p>
<h3 className="text-lg font-medium leading-none">Filename Settings</h3>
<p className="text-sm text-muted-foreground mt-1">Customize the output filenames.</p>
</div>
</AccordionTrigger>
<AccordionContent className="px-6 pb-6">
@@ -514,20 +512,20 @@ export function ImageConverter() {
<div className="flex items-center space-x-2">
<Switch id="use-default-base-name" checked={useDefaultBaseName} onCheckedChange={setUseDefaultBaseName} />
<Label htmlFor="use-default-base-name" className="flex items-center gap-1.5 cursor-pointer">
{t("settings.filename.useDefaultBaseName")}
Use default base name
<Tooltip>
<TooltipTrigger onClick={(e) => e.preventDefault()}>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.filename.useDefaultBaseNameTooltip")}</p>
<p>When enabled, all newly uploaded images will use the specified default base name.</p>
</TooltipContent>
</Tooltip>
</Label>
</div>
{useDefaultBaseName && (
<div className="space-y-2">
<Label htmlFor="default-base-name">{t("settings.filename.defaultBaseName")}</Label>
<Label htmlFor="default-base-name">Default base name</Label>
<div className="flex items-center gap-2">
<Input
id="default-base-name"
@@ -538,11 +536,11 @@ export function ImageConverter() {
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="sm" onClick={handleApplyDefaultBaseNameToAll} disabled={!defaultBaseName || !hasImages}>
{t("settings.filename.applyToAll")}
Apply to all
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.filename.applyToAllTooltip")}</p>
<p>Apply this base name to all currently uploaded images.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -550,13 +548,13 @@ export function ImageConverter() {
)}
<div className="space-y-2">
<div className="flex items-center gap-1.5">
<Label htmlFor="prefix">{t("settings.filename.prefix")}</Label>
<Label htmlFor="prefix">Prefix</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.filename.prefixTooltip")}</p>
<p>Add text to the beginning of every filename.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -564,13 +562,13 @@ export function ImageConverter() {
</div>
<div className="space-y-2">
<div className="flex items-center gap-1.5">
<Label htmlFor="suffix">{t("settings.filename.suffix")}</Label>
<Label htmlFor="suffix">Suffix</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.filename.suffixTooltip")}</p>
<p>Add text to the end of every filename (before the number).</p>
</TooltipContent>
</Tooltip>
</div>
@@ -579,13 +577,13 @@ export function ImageConverter() {
<div className="flex items-center space-x-2 pt-2">
<Switch id="use-counter" checked={useCounter} onCheckedChange={setUseCounter} />
<Label htmlFor="use-counter" className="flex items-center gap-1.5 cursor-pointer">
{t("settings.filename.addSequentialNumber")}
Add sequential number
<Tooltip>
<TooltipTrigger onClick={(e) => e.preventDefault()}>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.filename.addSequentialNumberTooltip")}</p>
<p>Append a numbered sequence to each filename.</p>
</TooltipContent>
</Tooltip>
</Label>
@@ -594,13 +592,13 @@ export function ImageConverter() {
<div className="grid grid-cols-2 gap-4 pt-2">
<div className="space-y-2">
<div className="flex items-center gap-1.5">
<Label htmlFor="counter-start">{t("settings.filename.startNumber")}</Label>
<Label htmlFor="counter-start">Start number</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.filename.startNumberTooltip")}</p>
<p>The first number to use in the sequence.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -614,13 +612,13 @@ export function ImageConverter() {
</div>
<div className="space-y-2">
<div className="flex items-center gap-1.5">
<Label htmlFor="counter-digits">{t("settings.filename.paddingDigits")}</Label>
<Label htmlFor="counter-digits">Padding digits</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.filename.paddingDigitsTooltip")}</p>
<p>Total number of digits for the counter, padded with leading zeros (e.g., 3 for 001).</p>
</TooltipContent>
</Tooltip>
</div>
@@ -641,21 +639,21 @@ export function ImageConverter() {
<AccordionItem value="quality-settings" className="border rounded-lg bg-card">
<AccordionTrigger className="p-6 hover:no-underline">
<div className="text-left">
<h3 className="text-lg font-medium leading-none">{t("settings.quality.title")}</h3>
<p className="text-sm text-muted-foreground mt-1">{t("settings.quality.description")}</p>
<h3 className="text-lg font-medium leading-none">Quality Settings</h3>
<p className="text-sm text-muted-foreground mt-1">Choose format and compression level.</p>
</div>
</AccordionTrigger>
<AccordionContent className="px-6 pb-6">
<div className="space-y-6">
<div className="space-y-2">
<div className="flex items-center gap-1.5">
<Label htmlFor="format">{t("settings.quality.format")}</Label>
<Label htmlFor="format">Format</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.quality.formatTooltip")}</p>
<p>Choose the output file format for the images.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -671,13 +669,13 @@ export function ImageConverter() {
<div className="space-y-2">
<div className="flex justify-between items-center">
<div className="flex items-center gap-1.5">
<Label htmlFor="quality">{t("settings.quality.quality")}</Label>
<Label htmlFor="quality">Quality</Label>
<Tooltip>
<TooltipTrigger>
<HelpCircle className="h-4 w-4 text-muted-foreground" />
</TooltipTrigger>
<TooltipContent>
<p>{t("settings.quality.qualityTooltip")}</p>
<p>Set compression quality for JPEG/WEBP. Higher is better quality but larger file size.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -693,7 +691,7 @@ export function ImageConverter() {
disabled={format === 'png'}
/>
{format === 'png' && (
<p className="text-xs text-muted-foreground pt-1">{t("settings.quality.pngHint")}</p>
<p className="text-xs text-muted-foreground pt-1">Quality slider is disabled for PNG (lossless format).</p>
)}
</div>
</div>
@@ -708,11 +706,11 @@ export function ImageConverter() {
className="w-full"
>
<Check className="mr-2 h-4 w-4" />
{t("applySettings")}
Apply Settings
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{t("applySettingsTooltip")}</p>
<p>Confirm and apply all the settings above. This does not download the images.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -721,7 +719,7 @@ export function ImageConverter() {
<Card>
<CardContent className="pt-6">
<div className="space-y-4">
<h3 className="text-lg font-medium">{t("uploadTitle")}</h3>
<h3 className="text-lg font-medium">Upload Images</h3>
<div
className={cn(
"w-full h-48 rounded-lg border-2 border-dashed flex items-center justify-center relative transition-colors cursor-pointer hover:border-primary/60",
@@ -734,8 +732,8 @@ export function ImageConverter() {
>
<div className="flex flex-col items-center justify-center text-center text-muted-foreground">
<Upload className="w-8 h-8 mb-2" />
<p className="font-semibold">{t("uploadButton")}</p>
<p className="text-xs text-muted-foreground mt-1">{t("uploadHint")}</p>
<p className="font-semibold">Click or drag and drop to upload</p>
<p className="text-xs text-muted-foreground mt-1">PNG, JPG, WEBP supported</p>
</div>
<Input type="file" ref={fileInputRef} onChange={handleImageChange} className="hidden" accept="image/*" multiple />
</div>
@@ -747,25 +745,25 @@ export function ImageConverter() {
<Card>
<CardHeader>
<div className="flex justify-between items-center">
<CardTitle>{t("uploadedImagesTitle")}</CardTitle>
<CardTitle>Uploaded Images</CardTitle>
<div className="flex items-center gap-2">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="sm" onClick={handleClearAll} disabled={isConverting || convertingIndex !== null}><Trash2 className="mr-2 h-4 w-4" />{t("clearAll")}</Button>
<Button variant="ghost" size="sm" onClick={handleClearAll} disabled={isConverting || convertingIndex !== null}><Trash2 className="mr-2 h-4 w-4" />Clear All</Button>
</TooltipTrigger>
<TooltipContent>
<p>{t("clearAllTooltip")}</p>
<p>Remove all uploaded images.</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button onClick={handleConvertAndDownloadAll} disabled={!hasImages || isConverting || convertingIndex !== null}>
<Download className="mr-2 h-4 w-4" />
{isConverting ? t("downloadAllConverting") : t("downloadAll", { count: images.length })}
{isConverting ? "Converting..." : `Download All (${images.length})`}
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{t("downloadAllTooltip")}</p>
<p>Convert and download all images with the current settings.</p>
</TooltipContent>
</Tooltip>
</div>
@@ -777,20 +775,19 @@ export function ImageConverter() {
const baseFilename = generateFinalFilename(index);
const dimensionSuffix = width && height ? `_${width}x${height}` : '';
const finalFilename = `${baseFilename}${dimensionSuffix}`;
const finalFilenameWithExt = `${finalFilename}.${format}`;
return (
<div key={url} className="p-4 border rounded-lg flex items-center gap-4">
<img src={url} alt={`Preview ${index + 1}`} className="w-20 h-20 object-cover rounded-md shrink-0" />
<div className="flex-1 min-w-0">
<Label htmlFor={`filename-${index}`} className="text-xs text-muted-foreground">{t("baseNameLabel")}</Label>
<Label htmlFor={`filename-${index}`} className="text-xs text-muted-foreground">Base Name</Label>
<Input
id={`filename-${index}`}
value={filenames[index]}
onChange={(e) => handleFilenameChange(index, e.target.value)}
className="text-sm font-medium h-8 mt-1"
/>
<p className="text-xs text-muted-foreground truncate mt-1" title={finalFilenameWithExt}>
{t("finalNameLabel", { filename: finalFilenameWithExt })}
<p className="text-xs text-muted-foreground truncate mt-1" title={`${finalFilename}.${format}`}>
Final name: {finalFilename}.{format}
</p>
</div>
<div className="flex items-center shrink-0">
@@ -807,7 +804,7 @@ export function ImageConverter() {
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{t("downloadImageTooltip")}</p>
<p>Download this image</p>
</TooltipContent>
</Tooltip>
<Tooltip>
@@ -823,7 +820,7 @@ export function ImageConverter() {
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{t("removeImageTooltip")}</p>
<p>Remove this image</p>
</TooltipContent>
</Tooltip>
</div>

View File

@@ -1,35 +0,0 @@
"use client";
import { useLocale } from "next-intl";
import { useRouter, usePathname } from "next-intl/client";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { useTranslations } from "next-intl";
export function LanguageSwitcher() {
const t = useTranslations("LanguageSwitcher");
const locale = useLocale();
const router = useRouter();
const pathname = usePathname();
const onSelectChange = (value: string) => {
router.replace(pathname, { locale: value });
};
return (
<Select onValueChange={onSelectChange} defaultValue={locale}>
<SelectTrigger className="w-[120px]">
<SelectValue placeholder={t("placeholder")} />
</SelectTrigger>
<SelectContent>
<SelectItem value="en">{t("en")}</SelectItem>
<SelectItem value="de">{t("de")}</SelectItem>
</SelectContent>
</Select>
);
}

View File

@@ -1,5 +0,0 @@
import {getRequestConfig} from 'next-intl/server';
export default getRequestConfig(async ({locale}) => ({
messages: (await import(`../messages/${locale}.json`)).default
}));