Reverted all changes back to version 1d41f871ee
This commit is contained in:
102
messages/de.json
102
messages/de.json
@@ -1,102 +0,0 @@
|
|||||||
{
|
|
||||||
"HomePage": {
|
|
||||||
"title": "Bild-Web-Exporter",
|
|
||||||
"description": "Laden Sie ein Bild hoch und exportieren Sie es in einer anderen Auflösung und einem anderen Format."
|
|
||||||
},
|
|
||||||
"LanguageSwitcher": {
|
|
||||||
"placeholder": "Sprache",
|
|
||||||
"en": "Englisch",
|
|
||||||
"de": "Deutsch"
|
|
||||||
},
|
|
||||||
"ImageConverter": {
|
|
||||||
"uploadTitle": "Bilder hochladen",
|
|
||||||
"uploadButton": "Klicken oder per Drag & Drop hochladen",
|
|
||||||
"uploadHint": "PNG, JPG, WEBP werden unterstützt",
|
|
||||||
"uploadedImagesTitle": "Hochgeladene Bilder",
|
|
||||||
"clearAll": "Alle löschen",
|
|
||||||
"clearAllTooltip": "Alle hochgeladenen Bilder entfernen.",
|
|
||||||
"downloadAll": "Alle herunterladen ({count})",
|
|
||||||
"downloadAllConverting": "Konvertiere...",
|
|
||||||
"downloadAllTooltip": "Alle Bilder mit den aktuellen Einstellungen konvertieren und herunterladen.",
|
|
||||||
"baseNameLabel": "Basisname",
|
|
||||||
"finalNameLabel": "Endgültiger Name: {filename}",
|
|
||||||
"downloadImageTooltip": "Dieses Bild herunterladen",
|
|
||||||
"removeImageTooltip": "Dieses Bild entfernen",
|
|
||||||
"applySettings": "Einstellungen anwenden",
|
|
||||||
"applySettingsTooltip": "Bestätigen und alle oben genannten Einstellungen anwenden. Dies lädt die Bilder nicht herunter.",
|
|
||||||
"settings": {
|
|
||||||
"image": {
|
|
||||||
"title": "Bildeinstellungen",
|
|
||||||
"description": "Passen Sie Auflösung und Skalierung für alle Bilder an.",
|
|
||||||
"aspectRatio": "Seitenverhältnis",
|
|
||||||
"aspectRatioTooltip": "Wählen Sie ein voreingestelltes Seitenverhältnis oder 'Benutzerdefiniert', um die Abmessungen manuell einzugeben.",
|
|
||||||
"aspectRatios": {
|
|
||||||
"custom": "Benutzerdefiniert",
|
|
||||||
"square": "1:1 (Quadratisch)",
|
|
||||||
"standard": "4:3 (Standard)",
|
|
||||||
"photography": "3:2 (Fotografie)",
|
|
||||||
"widescreen": "16:9 (Breitbild)"
|
|
||||||
},
|
|
||||||
"width": "Breite (px)",
|
|
||||||
"widthTooltip": "Legen Sie die Ausgabebreite in Pixel fest. Leer lassen, um die Originalbreite zu verwenden.",
|
|
||||||
"height": "Höhe (px)",
|
|
||||||
"heightTooltip": "Legen Sie die Ausgabehöhe in Pixel fest. Leer lassen, um die Originalhöhe zu verwenden.",
|
|
||||||
"swapDimensionsTooltip": "Die eingegebenen Breiten- und Höhenwerte tauschen.",
|
|
||||||
"keepOrientation": "Originalausrichtung beibehalten",
|
|
||||||
"keepOrientationTooltip": "Tauscht automatisch Breite und Höhe, um der Ausrichtung des Originalbildes zu entsprechen.",
|
|
||||||
"scaling": "Skalierung",
|
|
||||||
"scalingTooltip": "Bestimmt, wie das Bild in die neuen Abmessungen passt.",
|
|
||||||
"scalingOptions": {
|
|
||||||
"fill": "Füllen (strecken)",
|
|
||||||
"cover": "Abdecken (zuschneiden)",
|
|
||||||
"contain": "Enthalten (Letterbox)"
|
|
||||||
},
|
|
||||||
"position": "Position",
|
|
||||||
"positionTooltip": "Legt den Ankerpunkt für die Skalierung 'Abdecken' oder 'Enthalten' fest."
|
|
||||||
},
|
|
||||||
"filename": {
|
|
||||||
"title": "Dateiname-Einstellungen",
|
|
||||||
"description": "Passen Sie die Ausgabedateinamen an.",
|
|
||||||
"useDefaultBaseName": "Standard-Basisnamen verwenden",
|
|
||||||
"useDefaultBaseNameTooltip": "Wenn aktiviert, verwenden alle neu hochgeladenen Bilder den angegebenen Standard-Basisnamen.",
|
|
||||||
"defaultBaseName": "Standard-Basisname",
|
|
||||||
"applyToAll": "Auf alle anwenden",
|
|
||||||
"applyToAllTooltip": "Diesen Basisnamen auf alle aktuell hochgeladenen Bilder anwenden.",
|
|
||||||
"prefix": "Präfix",
|
|
||||||
"prefixTooltip": "Fügen Sie Text am Anfang jedes Dateinamens hinzu.",
|
|
||||||
"suffix": "Suffix",
|
|
||||||
"suffixTooltip": "Fügen Sie Text am Ende jedes Dateinamens hinzu (vor der Nummer).",
|
|
||||||
"addSequentialNumber": "Laufende Nummer hinzufügen",
|
|
||||||
"addSequentialNumberTooltip": "Fügen Sie jedem Dateinamen eine nummerierte Sequenz an.",
|
|
||||||
"startNumber": "Startnummer",
|
|
||||||
"startNumberTooltip": "Die erste Nummer, die in der Sequenz verwendet wird.",
|
|
||||||
"paddingDigits": "Auffüllziffern",
|
|
||||||
"paddingDigitsTooltip": "Gesamtzahl der Ziffern für den Zähler, mit führenden Nullen aufgefüllt (z. B. 3 für 001)."
|
|
||||||
},
|
|
||||||
"quality": {
|
|
||||||
"title": "Qualitätseinstellungen",
|
|
||||||
"description": "Wählen Sie Format und Komprimierungsstufe.",
|
|
||||||
"format": "Format",
|
|
||||||
"formatTooltip": "Wählen Sie das Ausgabedateiformat für die Bilder.",
|
|
||||||
"quality": "Qualität",
|
|
||||||
"qualityTooltip": "Stellen Sie die Komprimierungsqualität für JPEG/WEBP ein. Höher bedeutet bessere Qualität, aber größere Dateigröße.",
|
|
||||||
"pngHint": "Der Qualitätsschieberegler ist für PNG (verlustfreies Format) deaktiviert."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"toasts": {
|
|
||||||
"noImages": "Bitte laden Sie zuerst Bilder hoch.",
|
|
||||||
"conversionStarting": "Konvertierung für {count} Bilder wird gestartet...",
|
|
||||||
"conversionSuccess": "Alle {count} Bilder erfolgreich exportiert!",
|
|
||||||
"conversionError": "Während der Konvertierung ist ein unbekannter Fehler aufgetreten.",
|
|
||||||
"singleConversionStarting": "Konvertierung für {filename} wird gestartet...",
|
|
||||||
"singleConversionSuccess": "{filename} erfolgreich exportiert!",
|
|
||||||
"settingsApplied": "Einstellungen aktualisiert und werden für alle Downloads verwendet.",
|
|
||||||
"noValidImages": "Keine gültigen Bilddateien gefunden.",
|
|
||||||
"imagesAdded": "{count} Bild(er) hinzugefügt.",
|
|
||||||
"allCleared": "Alle Bilder gelöscht.",
|
|
||||||
"noDefaultBaseName": "Bitte geben Sie einen Standard-Basisnamen ein, um ihn anzuwenden.",
|
|
||||||
"uploadFirst": "Laden Sie zuerst einige Bilder hoch.",
|
|
||||||
"baseNameApplied": "Basisname für alle {count} Bilder auf \"{baseName}\" gesetzt."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
102
messages/en.json
102
messages/en.json
@@ -1,102 +0,0 @@
|
|||||||
{
|
|
||||||
"HomePage": {
|
|
||||||
"title": "Image Web Exporter",
|
|
||||||
"description": "Upload a picture, then export it in a different resolution and format."
|
|
||||||
},
|
|
||||||
"LanguageSwitcher": {
|
|
||||||
"placeholder": "Language",
|
|
||||||
"en": "English",
|
|
||||||
"de": "German"
|
|
||||||
},
|
|
||||||
"ImageConverter": {
|
|
||||||
"uploadTitle": "Upload Images",
|
|
||||||
"uploadButton": "Click or drag and drop to upload",
|
|
||||||
"uploadHint": "PNG, JPG, WEBP supported",
|
|
||||||
"uploadedImagesTitle": "Uploaded Images",
|
|
||||||
"clearAll": "Clear All",
|
|
||||||
"clearAllTooltip": "Remove all uploaded images.",
|
|
||||||
"downloadAll": "Download All ({count})",
|
|
||||||
"downloadAllConverting": "Converting...",
|
|
||||||
"downloadAllTooltip": "Convert and download all images with the current settings.",
|
|
||||||
"baseNameLabel": "Base Name",
|
|
||||||
"finalNameLabel": "Final name: {filename}",
|
|
||||||
"downloadImageTooltip": "Download this image",
|
|
||||||
"removeImageTooltip": "Remove this image",
|
|
||||||
"applySettings": "Apply Settings",
|
|
||||||
"applySettingsTooltip": "Confirm and apply all the settings above. This does not download the images.",
|
|
||||||
"settings": {
|
|
||||||
"image": {
|
|
||||||
"title": "Image Settings",
|
|
||||||
"description": "Adjust resolution and scaling for all images.",
|
|
||||||
"aspectRatio": "Aspect Ratio",
|
|
||||||
"aspectRatioTooltip": "Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.",
|
|
||||||
"aspectRatios": {
|
|
||||||
"custom": "Custom",
|
|
||||||
"square": "1:1 (Square)",
|
|
||||||
"standard": "4:3 (Standard)",
|
|
||||||
"photography": "3:2 (Photography)",
|
|
||||||
"widescreen": "16:9 (Widescreen)"
|
|
||||||
},
|
|
||||||
"width": "Width (px)",
|
|
||||||
"widthTooltip": "Set the output width in pixels. Leave blank to use the original width.",
|
|
||||||
"height": "Height (px)",
|
|
||||||
"heightTooltip": "Set the output height in pixels. Leave blank to use the original height.",
|
|
||||||
"swapDimensionsTooltip": "Swap the entered width and height values.",
|
|
||||||
"keepOrientation": "Keep original orientation",
|
|
||||||
"keepOrientationTooltip": "Automatically swaps width and height to match the original image's orientation.",
|
|
||||||
"scaling": "Scaling",
|
|
||||||
"scalingTooltip": "Determines how the image fits into the new dimensions.",
|
|
||||||
"scalingOptions": {
|
|
||||||
"fill": "Fill (stretch to fit)",
|
|
||||||
"cover": "Cover (crop to fit)",
|
|
||||||
"contain": "Contain (letterbox)"
|
|
||||||
},
|
|
||||||
"position": "Position",
|
|
||||||
"positionTooltip": "Sets the anchor point for 'Cover' or 'Contain' scaling."
|
|
||||||
},
|
|
||||||
"filename": {
|
|
||||||
"title": "Filename Settings",
|
|
||||||
"description": "Customize the output filenames.",
|
|
||||||
"useDefaultBaseName": "Use default base name",
|
|
||||||
"useDefaultBaseNameTooltip": "When enabled, all newly uploaded images will use the specified default base name.",
|
|
||||||
"defaultBaseName": "Default base name",
|
|
||||||
"applyToAll": "Apply to all",
|
|
||||||
"applyToAllTooltip": "Apply this base name to all currently uploaded images.",
|
|
||||||
"prefix": "Prefix",
|
|
||||||
"prefixTooltip": "Add text to the beginning of every filename.",
|
|
||||||
"suffix": "Suffix",
|
|
||||||
"suffixTooltip": "Add text to the end of every filename (before the number).",
|
|
||||||
"addSequentialNumber": "Add sequential number",
|
|
||||||
"addSequentialNumberTooltip": "Append a numbered sequence to each filename.",
|
|
||||||
"startNumber": "Start number",
|
|
||||||
"startNumberTooltip": "The first number to use in the sequence.",
|
|
||||||
"paddingDigits": "Padding digits",
|
|
||||||
"paddingDigitsTooltip": "Total number of digits for the counter, padded with leading zeros (e.g., 3 for 001)."
|
|
||||||
},
|
|
||||||
"quality": {
|
|
||||||
"title": "Quality Settings",
|
|
||||||
"description": "Choose format and compression level.",
|
|
||||||
"format": "Format",
|
|
||||||
"formatTooltip": "Choose the output file format for the images.",
|
|
||||||
"quality": "Quality",
|
|
||||||
"qualityTooltip": "Set compression quality for JPEG/WEBP. Higher is better quality but larger file size.",
|
|
||||||
"pngHint": "Quality slider is disabled for PNG (lossless format)."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"toasts": {
|
|
||||||
"noImages": "Please upload images first.",
|
|
||||||
"conversionStarting": "Starting conversion for {count} images...",
|
|
||||||
"conversionSuccess": "Successfully exported all {count} images!",
|
|
||||||
"conversionError": "An unknown error occurred during conversion.",
|
|
||||||
"singleConversionStarting": "Starting conversion for {filename}...",
|
|
||||||
"singleConversionSuccess": "Successfully exported {filename}!",
|
|
||||||
"settingsApplied": "Settings updated and will be used for all downloads.",
|
|
||||||
"noValidImages": "No valid image files found.",
|
|
||||||
"imagesAdded": "{count} image(s) added.",
|
|
||||||
"allCleared": "All images cleared.",
|
|
||||||
"noDefaultBaseName": "Please enter a default base name to apply.",
|
|
||||||
"uploadFirst": "Upload some images first.",
|
|
||||||
"baseNameApplied": "Set base name to \"{baseName}\" for all {count} images."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import createMiddleware from 'next-intl/middleware';
|
|
||||||
|
|
||||||
export default createMiddleware({
|
|
||||||
locales: ['en', 'de'],
|
|
||||||
defaultLocale: 'en'
|
|
||||||
});
|
|
||||||
|
|
||||||
export const config = {
|
|
||||||
matcher: ['/', '/(de|en)/:path*']
|
|
||||||
};
|
|
||||||
@@ -44,7 +44,6 @@
|
|||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"lucide-react": "^0.511.0",
|
"lucide-react": "^0.511.0",
|
||||||
"next": "15.3.8",
|
"next": "15.3.8",
|
||||||
"next-intl": "^4.7.0",
|
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"react": "^19.2.1",
|
"react": "^19.2.1",
|
||||||
"react-day-picker": "^8.10.1",
|
"react-day-picker": "^8.10.1",
|
||||||
|
|||||||
403
pnpm-lock.yaml
generated
403
pnpm-lock.yaml
generated
@@ -113,9 +113,6 @@ importers:
|
|||||||
next:
|
next:
|
||||||
specifier: 15.3.8
|
specifier: 15.3.8
|
||||||
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
version: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||||
next-intl:
|
|
||||||
specifier: ^4.7.0
|
|
||||||
version: 4.7.0(next@15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(typescript@5.8.3)
|
|
||||||
next-themes:
|
next-themes:
|
||||||
specifier: ^0.4.6
|
specifier: ^0.4.6
|
||||||
version: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
version: 0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
||||||
@@ -225,24 +222,6 @@ packages:
|
|||||||
'@floating-ui/utils@0.2.9':
|
'@floating-ui/utils@0.2.9':
|
||||||
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
resolution: {integrity: sha512-MDWhGtE+eHw5JW7lq4qhc5yRLS11ERl1c7Z6Xd0a58DozHES6EnNNwUWbMiG4J9Cgj053Bhk8zvlhFYKVhULwg==}
|
||||||
|
|
||||||
'@formatjs/ecma402-abstract@2.3.6':
|
|
||||||
resolution: {integrity: sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==}
|
|
||||||
|
|
||||||
'@formatjs/fast-memoize@2.2.7':
|
|
||||||
resolution: {integrity: sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==}
|
|
||||||
|
|
||||||
'@formatjs/icu-messageformat-parser@2.11.4':
|
|
||||||
resolution: {integrity: sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==}
|
|
||||||
|
|
||||||
'@formatjs/icu-skeleton-parser@1.8.16':
|
|
||||||
resolution: {integrity: sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==}
|
|
||||||
|
|
||||||
'@formatjs/intl-localematcher@0.5.10':
|
|
||||||
resolution: {integrity: sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==}
|
|
||||||
|
|
||||||
'@formatjs/intl-localematcher@0.6.2':
|
|
||||||
resolution: {integrity: sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==}
|
|
||||||
|
|
||||||
'@hookform/resolvers@5.0.1':
|
'@hookform/resolvers@5.0.1':
|
||||||
resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==}
|
resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -452,88 +431,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.4':
|
|
||||||
resolution: {integrity: sha512-hoh0vx4v+b3BNI7Cjoy2/B0ARqcwVNrzN/n7DLq9ZB4I3lrsvhrkCViJyfTj/Qi5xM9YFiH4AmHGK6pgH1ss7g==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [android]
|
|
||||||
|
|
||||||
'@parcel/watcher-darwin-arm64@2.5.4':
|
|
||||||
resolution: {integrity: sha512-kphKy377pZiWpAOyTgQYPE5/XEKVMaj6VUjKT5VkNyUJlr2qZAn8gIc7CPzx+kbhvqHDT9d7EqdOqRXT6vk0zw==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
'@parcel/watcher-darwin-x64@2.5.4':
|
|
||||||
resolution: {integrity: sha512-UKaQFhCtNJW1A9YyVz3Ju7ydf6QgrpNQfRZ35wNKUhTQ3dxJ/3MULXN5JN/0Z80V/KUBDGa3RZaKq1EQT2a2gg==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
'@parcel/watcher-freebsd-x64@2.5.4':
|
|
||||||
resolution: {integrity: sha512-Dib0Wv3Ow/m2/ttvLdeI2DBXloO7t3Z0oCp4bAb2aqyqOjKPPGrg10pMJJAQ7tt8P4V2rwYwywkDhUia/FgS+Q==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [freebsd]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-glibc@2.5.4':
|
|
||||||
resolution: {integrity: sha512-I5Vb769pdf7Q7Sf4KNy8Pogl/URRCKu9ImMmnVKYayhynuyGYMzuI4UOWnegQNa2sGpsPSbzDsqbHNMyeyPCgw==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [arm]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-musl@2.5.4':
|
|
||||||
resolution: {integrity: sha512-kGO8RPvVrcAotV4QcWh8kZuHr9bXi9a3bSZw7kFarYR0+fGliU7hd/zevhjw8fnvIKG3J9EO5G6sXNGCSNMYPQ==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [arm]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.4':
|
|
||||||
resolution: {integrity: sha512-KU75aooXhqGFY2W5/p8DYYHt4hrjHZod8AhcGAmhzPn/etTa+lYCDB2b1sJy3sWJ8ahFVTdy+EbqSBvMx3iFlw==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.4':
|
|
||||||
resolution: {integrity: sha512-Qx8uNiIekVutnzbVdrgSanM+cbpDD3boB1f8vMtnuG5Zau4/bdDbXyKwIn0ToqFhIuob73bcxV9NwRm04/hzHQ==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.4':
|
|
||||||
resolution: {integrity: sha512-UYBQvhYmgAv61LNUn24qGQdjtycFBKSK3EXr72DbJqX9aaLbtCOO8+1SkKhD/GNiJ97ExgcHBrukcYhVjrnogA==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.4':
|
|
||||||
resolution: {integrity: sha512-YoRWCVgxv8akZrMhdyVi6/TyoeeMkQ0PGGOf2E4omODrvd1wxniXP+DBynKoHryStks7l+fDAMUBRzqNHrVOpg==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-arm64@2.5.4':
|
|
||||||
resolution: {integrity: sha512-iby+D/YNXWkiQNYcIhg8P5hSjzXEHaQrk2SLrWOUD7VeC4Ohu0WQvmV+HDJokZVJ2UjJ4AGXW3bx7Lls9Ln4TQ==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-ia32@2.5.4':
|
|
||||||
resolution: {integrity: sha512-vQN+KIReG0a2ZDpVv8cgddlf67J8hk1WfZMMP7sMeZmJRSmEax5xNDNWKdgqSe2brOKTQQAs3aCCUal2qBHAyg==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [ia32]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-x64@2.5.4':
|
|
||||||
resolution: {integrity: sha512-3A6efb6BOKwyw7yk9ro2vus2YTt2nvcd56AuzxdMiVOxL9umDyN5PKkKfZ/gZ9row41SjVmTVQNWQhaRRGpOKw==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@parcel/watcher@2.5.4':
|
|
||||||
resolution: {integrity: sha512-WYa2tUVV5HiArWPB3ydlOc4R2ivq0IDrlqhMi3l7mVsFEXNcTfxYFPIHXHXIh/ca/y/V5N4E1zecyxdIBjYnkQ==}
|
|
||||||
engines: {node: '>= 10.0.0'}
|
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
@@ -1150,90 +1047,15 @@ packages:
|
|||||||
'@radix-ui/rect@1.1.1':
|
'@radix-ui/rect@1.1.1':
|
||||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||||
|
|
||||||
'@schummar/icu-type-parser@1.21.5':
|
|
||||||
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
|
|
||||||
|
|
||||||
'@standard-schema/utils@0.3.0':
|
'@standard-schema/utils@0.3.0':
|
||||||
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
|
resolution: {integrity: sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==}
|
||||||
|
|
||||||
'@swc/core-darwin-arm64@1.15.8':
|
|
||||||
resolution: {integrity: sha512-M9cK5GwyWWRkRGwwCbREuj6r8jKdES/haCZ3Xckgkl8MUQJZA3XB7IXXK1IXRNeLjg6m7cnoMICpXv1v1hlJOg==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
'@swc/core-darwin-x64@1.15.8':
|
|
||||||
resolution: {integrity: sha512-j47DasuOvXl80sKJHSi2X25l44CMc3VDhlJwA7oewC1nV1VsSzwX+KOwE5tLnfORvVJJyeiXgJORNYg4jeIjYQ==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [darwin]
|
|
||||||
|
|
||||||
'@swc/core-linux-arm-gnueabihf@1.15.8':
|
|
||||||
resolution: {integrity: sha512-siAzDENu2rUbwr9+fayWa26r5A9fol1iORG53HWxQL1J8ym4k7xt9eME0dMPXlYZDytK5r9sW8zEA10F2U3Xwg==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [arm]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@swc/core-linux-arm64-gnu@1.15.8':
|
|
||||||
resolution: {integrity: sha512-o+1y5u6k2FfPYbTRUPvurwzNt5qd0NTumCTFscCNuBksycloXY16J8L+SMW5QRX59n4Hp9EmFa3vpvNHRVv1+Q==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@swc/core-linux-arm64-musl@1.15.8':
|
|
||||||
resolution: {integrity: sha512-koiCqL09EwOP1S2RShCI7NbsQuG6r2brTqUYE7pV7kZm9O17wZ0LSz22m6gVibpwEnw8jI3IE1yYsQTVpluALw==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@swc/core-linux-x64-gnu@1.15.8':
|
|
||||||
resolution: {integrity: sha512-4p6lOMU3bC+Vd5ARtKJ/FxpIC5G8v3XLoPEZ5s7mLR8h7411HWC/LmTXDHcrSXRC55zvAVia1eldy6zDLz8iFQ==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@swc/core-linux-x64-musl@1.15.8':
|
|
||||||
resolution: {integrity: sha512-z3XBnbrZAL+6xDGAhJoN4lOueIxC/8rGrJ9tg+fEaeqLEuAtHSW2QHDHxDwkxZMjuF/pZ6MUTjHjbp8wLbuRLA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [linux]
|
|
||||||
|
|
||||||
'@swc/core-win32-arm64-msvc@1.15.8':
|
|
||||||
resolution: {integrity: sha512-djQPJ9Rh9vP8GTS/Df3hcc6XP6xnG5c8qsngWId/BLA9oX6C7UzCPAn74BG/wGb9a6j4w3RINuoaieJB3t+7iQ==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@swc/core-win32-ia32-msvc@1.15.8':
|
|
||||||
resolution: {integrity: sha512-/wfAgxORg2VBaUoFdytcVBVCgf1isWZIEXB9MZEUty4wwK93M/PxAkjifOho9RN3WrM3inPLabICRCEgdHpKKQ==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [ia32]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@swc/core-win32-x64-msvc@1.15.8':
|
|
||||||
resolution: {integrity: sha512-GpMePrh9Sl4d61o4KAHOOv5is5+zt6BEXCOCgs/H0FLGeii7j9bWDE8ExvKFy2GRRZVNR1ugsnzaGWHKM6kuzA==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
cpu: [x64]
|
|
||||||
os: [win32]
|
|
||||||
|
|
||||||
'@swc/core@1.15.8':
|
|
||||||
resolution: {integrity: sha512-T8keoJjXaSUoVBCIjgL6wAnhADIb09GOELzKg10CjNg+vLX48P93SME6jTfte9MZIm5m+Il57H3rTSk/0kzDUw==}
|
|
||||||
engines: {node: '>=10'}
|
|
||||||
peerDependencies:
|
|
||||||
'@swc/helpers': '>=0.5.17'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@swc/helpers':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/counter@0.1.3':
|
'@swc/counter@0.1.3':
|
||||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==}
|
||||||
|
|
||||||
'@swc/types@0.1.25':
|
|
||||||
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
|
|
||||||
|
|
||||||
'@types/d3-array@3.2.1':
|
'@types/d3-array@3.2.1':
|
||||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||||
|
|
||||||
@@ -1526,9 +1348,6 @@ packages:
|
|||||||
decimal.js-light@2.5.1:
|
decimal.js-light@2.5.1:
|
||||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||||
|
|
||||||
decimal.js@10.6.0:
|
|
||||||
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
|
||||||
|
|
||||||
detect-libc@2.0.4:
|
detect-libc@2.0.4:
|
||||||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -1680,9 +1499,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
intl-messageformat@10.7.18:
|
|
||||||
resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==}
|
|
||||||
|
|
||||||
is-arrayish@0.3.2:
|
is-arrayish@0.3.2:
|
||||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||||
|
|
||||||
@@ -1797,26 +1613,9 @@ packages:
|
|||||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
negotiator@1.0.0:
|
|
||||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
|
||||||
engines: {node: '>= 0.6'}
|
|
||||||
|
|
||||||
neo-async@2.6.2:
|
neo-async@2.6.2:
|
||||||
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
|
||||||
|
|
||||||
next-intl-swc-plugin-extractor@4.7.0:
|
|
||||||
resolution: {integrity: sha512-iAqflu2FWdQMWhwB0B2z52X7LmEpvnMNJXqVERZQ7bK5p9iqQLu70ur6Ka6NfiXLxfb+AeAkUX5qIciQOg+87A==}
|
|
||||||
|
|
||||||
next-intl@4.7.0:
|
|
||||||
resolution: {integrity: sha512-gvROzcNr/HM0jTzQlKWQxUNk8jrZ0bREz+bht3wNbv+uzlZ5Kn3J+m+viosub18QJ72S08UJnVK50PXWcUvwpQ==}
|
|
||||||
peerDependencies:
|
|
||||||
next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0
|
|
||||||
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
|
|
||||||
typescript: ^5.0.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
typescript:
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
next-themes@0.4.6:
|
next-themes@0.4.6:
|
||||||
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -1844,9 +1643,6 @@ packages:
|
|||||||
sass:
|
sass:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
node-addon-api@7.1.1:
|
|
||||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
|
||||||
|
|
||||||
node-releases@2.0.19:
|
node-releases@2.0.19:
|
||||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||||
|
|
||||||
@@ -1883,10 +1679,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||||
engines: {node: '>=8.6'}
|
engines: {node: '>=8.6'}
|
||||||
|
|
||||||
picomatch@4.0.3:
|
|
||||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
|
|
||||||
pify@2.3.0:
|
pify@2.3.0:
|
||||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@@ -1895,9 +1687,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
po-parser@2.1.1:
|
|
||||||
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
|
|
||||||
|
|
||||||
postcss-import@15.1.0:
|
postcss-import@15.1.0:
|
||||||
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
||||||
engines: {node: '>=14.0.0'}
|
engines: {node: '>=14.0.0'}
|
||||||
@@ -2242,11 +2031,6 @@ packages:
|
|||||||
'@types/react':
|
'@types/react':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
use-intl@4.7.0:
|
|
||||||
resolution: {integrity: sha512-jyd8nSErVRRsSlUa+SDobKHo9IiWs5fjcPl9VBUnzUyEQpVM5mwJCgw8eUiylhvBpLQzUGox1KN0XlRivSID9A==}
|
|
||||||
peerDependencies:
|
|
||||||
react: ^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0
|
|
||||||
|
|
||||||
use-sidecar@1.1.3:
|
use-sidecar@1.1.3:
|
||||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2361,36 +2145,6 @@ snapshots:
|
|||||||
|
|
||||||
'@floating-ui/utils@0.2.9': {}
|
'@floating-ui/utils@0.2.9': {}
|
||||||
|
|
||||||
'@formatjs/ecma402-abstract@2.3.6':
|
|
||||||
dependencies:
|
|
||||||
'@formatjs/fast-memoize': 2.2.7
|
|
||||||
'@formatjs/intl-localematcher': 0.6.2
|
|
||||||
decimal.js: 10.6.0
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@formatjs/fast-memoize@2.2.7':
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@formatjs/icu-messageformat-parser@2.11.4':
|
|
||||||
dependencies:
|
|
||||||
'@formatjs/ecma402-abstract': 2.3.6
|
|
||||||
'@formatjs/icu-skeleton-parser': 1.8.16
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@formatjs/icu-skeleton-parser@1.8.16':
|
|
||||||
dependencies:
|
|
||||||
'@formatjs/ecma402-abstract': 2.3.6
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@formatjs/intl-localematcher@0.5.10':
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@formatjs/intl-localematcher@0.6.2':
|
|
||||||
dependencies:
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
'@hookform/resolvers@5.0.1(react-hook-form@7.56.4(react@19.2.1))':
|
'@hookform/resolvers@5.0.1(react-hook-form@7.56.4(react@19.2.1))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@standard-schema/utils': 0.3.0
|
'@standard-schema/utils': 0.3.0
|
||||||
@@ -2546,66 +2300,6 @@ snapshots:
|
|||||||
'@nodelib/fs.scandir': 2.1.5
|
'@nodelib/fs.scandir': 2.1.5
|
||||||
fastq: 1.19.1
|
fastq: 1.19.1
|
||||||
|
|
||||||
'@parcel/watcher-android-arm64@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-darwin-arm64@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-darwin-x64@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-freebsd-x64@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-glibc@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm-musl@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-glibc@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-arm64-musl@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-glibc@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-linux-x64-musl@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-arm64@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-ia32@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher-win32-x64@2.5.4':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@parcel/watcher@2.5.4':
|
|
||||||
dependencies:
|
|
||||||
detect-libc: 2.0.4
|
|
||||||
is-glob: 4.0.3
|
|
||||||
node-addon-api: 7.1.1
|
|
||||||
picomatch: 4.0.3
|
|
||||||
optionalDependencies:
|
|
||||||
'@parcel/watcher-android-arm64': 2.5.4
|
|
||||||
'@parcel/watcher-darwin-arm64': 2.5.4
|
|
||||||
'@parcel/watcher-darwin-x64': 2.5.4
|
|
||||||
'@parcel/watcher-freebsd-x64': 2.5.4
|
|
||||||
'@parcel/watcher-linux-arm-glibc': 2.5.4
|
|
||||||
'@parcel/watcher-linux-arm-musl': 2.5.4
|
|
||||||
'@parcel/watcher-linux-arm64-glibc': 2.5.4
|
|
||||||
'@parcel/watcher-linux-arm64-musl': 2.5.4
|
|
||||||
'@parcel/watcher-linux-x64-glibc': 2.5.4
|
|
||||||
'@parcel/watcher-linux-x64-musl': 2.5.4
|
|
||||||
'@parcel/watcher-win32-arm64': 2.5.4
|
|
||||||
'@parcel/watcher-win32-ia32': 2.5.4
|
|
||||||
'@parcel/watcher-win32-x64': 2.5.4
|
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -3262,66 +2956,14 @@ snapshots:
|
|||||||
|
|
||||||
'@radix-ui/rect@1.1.1': {}
|
'@radix-ui/rect@1.1.1': {}
|
||||||
|
|
||||||
'@schummar/icu-type-parser@1.21.5': {}
|
|
||||||
|
|
||||||
'@standard-schema/utils@0.3.0': {}
|
'@standard-schema/utils@0.3.0': {}
|
||||||
|
|
||||||
'@swc/core-darwin-arm64@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-darwin-x64@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-linux-arm-gnueabihf@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-linux-arm64-gnu@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-linux-arm64-musl@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-linux-x64-gnu@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-linux-x64-musl@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-win32-arm64-msvc@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-win32-ia32-msvc@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core-win32-x64-msvc@1.15.8':
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
'@swc/core@1.15.8':
|
|
||||||
dependencies:
|
|
||||||
'@swc/counter': 0.1.3
|
|
||||||
'@swc/types': 0.1.25
|
|
||||||
optionalDependencies:
|
|
||||||
'@swc/core-darwin-arm64': 1.15.8
|
|
||||||
'@swc/core-darwin-x64': 1.15.8
|
|
||||||
'@swc/core-linux-arm-gnueabihf': 1.15.8
|
|
||||||
'@swc/core-linux-arm64-gnu': 1.15.8
|
|
||||||
'@swc/core-linux-arm64-musl': 1.15.8
|
|
||||||
'@swc/core-linux-x64-gnu': 1.15.8
|
|
||||||
'@swc/core-linux-x64-musl': 1.15.8
|
|
||||||
'@swc/core-win32-arm64-msvc': 1.15.8
|
|
||||||
'@swc/core-win32-ia32-msvc': 1.15.8
|
|
||||||
'@swc/core-win32-x64-msvc': 1.15.8
|
|
||||||
|
|
||||||
'@swc/counter@0.1.3': {}
|
'@swc/counter@0.1.3': {}
|
||||||
|
|
||||||
'@swc/helpers@0.5.15':
|
'@swc/helpers@0.5.15':
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.8.1
|
tslib: 2.8.1
|
||||||
|
|
||||||
'@swc/types@0.1.25':
|
|
||||||
dependencies:
|
|
||||||
'@swc/counter': 0.1.3
|
|
||||||
|
|
||||||
'@types/d3-array@3.2.1': {}
|
'@types/d3-array@3.2.1': {}
|
||||||
|
|
||||||
'@types/d3-color@3.1.3': {}
|
'@types/d3-color@3.1.3': {}
|
||||||
@@ -3630,9 +3272,8 @@ snapshots:
|
|||||||
|
|
||||||
decimal.js-light@2.5.1: {}
|
decimal.js-light@2.5.1: {}
|
||||||
|
|
||||||
decimal.js@10.6.0: {}
|
detect-libc@2.0.4:
|
||||||
|
optional: true
|
||||||
detect-libc@2.0.4: {}
|
|
||||||
|
|
||||||
detect-node-es@1.1.0: {}
|
detect-node-es@1.1.0: {}
|
||||||
|
|
||||||
@@ -3761,13 +3402,6 @@ snapshots:
|
|||||||
|
|
||||||
internmap@2.0.3: {}
|
internmap@2.0.3: {}
|
||||||
|
|
||||||
intl-messageformat@10.7.18:
|
|
||||||
dependencies:
|
|
||||||
'@formatjs/ecma402-abstract': 2.3.6
|
|
||||||
'@formatjs/fast-memoize': 2.2.7
|
|
||||||
'@formatjs/icu-messageformat-parser': 2.11.4
|
|
||||||
tslib: 2.8.1
|
|
||||||
|
|
||||||
is-arrayish@0.3.2:
|
is-arrayish@0.3.2:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -3862,28 +3496,8 @@ snapshots:
|
|||||||
|
|
||||||
nanoid@3.3.11: {}
|
nanoid@3.3.11: {}
|
||||||
|
|
||||||
negotiator@1.0.0: {}
|
|
||||||
|
|
||||||
neo-async@2.6.2: {}
|
neo-async@2.6.2: {}
|
||||||
|
|
||||||
next-intl-swc-plugin-extractor@4.7.0: {}
|
|
||||||
|
|
||||||
next-intl@4.7.0(next@15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1))(react@19.2.1)(typescript@5.8.3):
|
|
||||||
dependencies:
|
|
||||||
'@formatjs/intl-localematcher': 0.5.10
|
|
||||||
'@parcel/watcher': 2.5.4
|
|
||||||
'@swc/core': 1.15.8
|
|
||||||
negotiator: 1.0.0
|
|
||||||
next: 15.3.8(react-dom@19.2.1(react@19.2.1))(react@19.2.1)
|
|
||||||
next-intl-swc-plugin-extractor: 4.7.0
|
|
||||||
po-parser: 2.1.1
|
|
||||||
react: 19.2.1
|
|
||||||
use-intl: 4.7.0(react@19.2.1)
|
|
||||||
optionalDependencies:
|
|
||||||
typescript: 5.8.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@swc/helpers'
|
|
||||||
|
|
||||||
next-themes@0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
|
next-themes@0.4.6(react-dom@19.2.1(react@19.2.1))(react@19.2.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.1
|
react: 19.2.1
|
||||||
@@ -3914,8 +3528,6 @@ snapshots:
|
|||||||
- '@babel/core'
|
- '@babel/core'
|
||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
|
|
||||||
node-addon-api@7.1.1: {}
|
|
||||||
|
|
||||||
node-releases@2.0.19: {}
|
node-releases@2.0.19: {}
|
||||||
|
|
||||||
normalize-path@3.0.0: {}
|
normalize-path@3.0.0: {}
|
||||||
@@ -3939,14 +3551,10 @@ snapshots:
|
|||||||
|
|
||||||
picomatch@2.3.1: {}
|
picomatch@2.3.1: {}
|
||||||
|
|
||||||
picomatch@4.0.3: {}
|
|
||||||
|
|
||||||
pify@2.3.0: {}
|
pify@2.3.0: {}
|
||||||
|
|
||||||
pirates@4.0.7: {}
|
pirates@4.0.7: {}
|
||||||
|
|
||||||
po-parser@2.1.1: {}
|
|
||||||
|
|
||||||
postcss-import@15.1.0(postcss@8.5.3):
|
postcss-import@15.1.0(postcss@8.5.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss: 8.5.3
|
postcss: 8.5.3
|
||||||
@@ -4313,13 +3921,6 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/react': 19.1.5
|
'@types/react': 19.1.5
|
||||||
|
|
||||||
use-intl@4.7.0(react@19.2.1):
|
|
||||||
dependencies:
|
|
||||||
'@formatjs/fast-memoize': 2.2.7
|
|
||||||
'@schummar/icu-type-parser': 1.21.5
|
|
||||||
intl-messageformat: 10.7.18
|
|
||||||
react: 19.2.1
|
|
||||||
|
|
||||||
use-sidecar@1.1.3(@types/react@19.1.5)(react@19.2.1):
|
use-sidecar@1.1.3(@types/react@19.1.5)(react@19.2.1):
|
||||||
dependencies:
|
dependencies:
|
||||||
detect-node-es: 1.1.0
|
detect-node-es: 1.1.0
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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 = {
|
const geistSans = Geist({
|
||||||
children: ReactNode;
|
variable: "--font-geist-sans",
|
||||||
params: { locale: string };
|
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
|
export default function RootLayout({
|
||||||
// of this file fixes an issue in Next.js 13.4 where link clicks that switch
|
children,
|
||||||
// the locale would otherwise cause a full page reload.
|
}: Readonly<{
|
||||||
export default function RootLayout({ children, params: { locale } }: Props) {
|
children: React.ReactNode;
|
||||||
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang={locale}>
|
<html lang="en" suppressHydrationWarning>
|
||||||
<body>{children}</body>
|
<body
|
||||||
|
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||||
|
>
|
||||||
|
<ThemeProvider
|
||||||
|
attribute="class"
|
||||||
|
defaultTheme="system"
|
||||||
|
enableSystem
|
||||||
|
disableTransitionOnChange
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<Toaster />
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,22 +1,15 @@
|
|||||||
import { ImageConverter } from "@/components/image-converter";
|
import { ImageConverter } from "@/components/image-converter";
|
||||||
import { LanguageSwitcher } from "@/components/language-switcher";
|
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
const t = useTranslations("HomePage");
|
|
||||||
|
|
||||||
return (
|
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="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">
|
<main className="flex flex-col items-center w-full max-w-6xl z-10">
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-5xl">
|
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-5xl">
|
||||||
{t("title")}
|
Image Web Exporter
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-3 text-lg text-gray-600 dark:text-gray-400">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ImageConverter />
|
<ImageConverter />
|
||||||
@@ -36,18 +36,16 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
const aspectRatios = [
|
const aspectRatios = [
|
||||||
{ name: "custom", value: "custom" },
|
{ name: "Custom", value: "custom" },
|
||||||
{ name: "square", value: "1/1" },
|
{ name: "1:1 (Square)", value: "1/1" },
|
||||||
{ name: "standard", value: "4/3" },
|
{ name: "4:3 (Standard)", value: "4/3" },
|
||||||
{ name: "photography", value: "3/2" },
|
{ name: "3:2 (Photography)", value: "3/2" },
|
||||||
{ name: "widescreen", value: "16/9" },
|
{ name: "16:9 (Widescreen)", value: "16/9" },
|
||||||
];
|
];
|
||||||
|
|
||||||
export function ImageConverter() {
|
export function ImageConverter() {
|
||||||
const t = useTranslations("ImageConverter");
|
|
||||||
const [images, setImages] = useState<File[]>([]);
|
const [images, setImages] = useState<File[]>([]);
|
||||||
const [previewUrls, setPreviewUrls] = useState<string[]>([]);
|
const [previewUrls, setPreviewUrls] = useState<string[]>([]);
|
||||||
const [filenames, setFilenames] = useState<string[]>([]);
|
const [filenames, setFilenames] = useState<string[]>([]);
|
||||||
@@ -88,7 +86,7 @@ export function ImageConverter() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (imageFiles.length === 0) {
|
if (imageFiles.length === 0) {
|
||||||
toast.error(t("toasts.noValidImages"));
|
toast.error("No valid image files found.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,7 +108,7 @@ export function ImageConverter() {
|
|||||||
setPreviewUrls(newPreviewUrls);
|
setPreviewUrls(newPreviewUrls);
|
||||||
setFilenames(newFilenames);
|
setFilenames(newFilenames);
|
||||||
|
|
||||||
toast.success(t("toasts.imagesAdded", { count: imageFiles.length }));
|
toast.success(`${imageFiles.length} image(s) added.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handleImageChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
@@ -150,7 +148,7 @@ export function ImageConverter() {
|
|||||||
setImages([]);
|
setImages([]);
|
||||||
setPreviewUrls([]);
|
setPreviewUrls([]);
|
||||||
setFilenames([]);
|
setFilenames([]);
|
||||||
toast.info(t("toasts.allCleared"));
|
toast.info("All images cleared.");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilenameChange = (index: number, newName: string) => {
|
const handleFilenameChange = (index: number, newName: string) => {
|
||||||
@@ -262,12 +260,12 @@ export function ImageConverter() {
|
|||||||
|
|
||||||
const handleConvertAndDownloadAll = async () => {
|
const handleConvertAndDownloadAll = async () => {
|
||||||
if (images.length === 0) {
|
if (images.length === 0) {
|
||||||
toast.error(t("toasts.noImages"));
|
toast.error("Please upload images first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsConverting(true);
|
setIsConverting(true);
|
||||||
toast.info(t("toasts.conversionStarting", { count: images.length }));
|
toast.info(`Starting conversion for ${images.length} images...`);
|
||||||
|
|
||||||
const conversionPromises = images.map((image, index) =>
|
const conversionPromises = images.map((image, index) =>
|
||||||
convertAndDownload(image, previewUrls[index], index)
|
convertAndDownload(image, previewUrls[index], index)
|
||||||
@@ -275,12 +273,12 @@ export function ImageConverter() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await Promise.all(conversionPromises);
|
await Promise.all(conversionPromises);
|
||||||
toast.success(t("toasts.conversionSuccess", { count: images.length }));
|
toast.success(`Successfully exported all ${images.length} images!`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
} else {
|
} else {
|
||||||
toast.error(t("toasts.conversionError"));
|
toast.error("An unknown error occurred during conversion.");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setIsConverting(false);
|
setIsConverting(false);
|
||||||
@@ -289,16 +287,16 @@ export function ImageConverter() {
|
|||||||
|
|
||||||
const handleConvertAndDownloadSingle = async (index: number) => {
|
const handleConvertAndDownloadSingle = async (index: number) => {
|
||||||
setConvertingIndex(index);
|
setConvertingIndex(index);
|
||||||
toast.info(t("toasts.singleConversionStarting", { filename: filenames[index] }));
|
toast.info(`Starting conversion for ${filenames[index]}...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await convertAndDownload(images[index], previewUrls[index], index);
|
await convertAndDownload(images[index], previewUrls[index], index);
|
||||||
toast.success(t("toasts.singleConversionSuccess", { filename: filenames[index] }));
|
toast.success(`Successfully exported ${filenames[index]}!`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof Error) {
|
if (error instanceof Error) {
|
||||||
toast.error(error.message);
|
toast.error(error.message);
|
||||||
} else {
|
} else {
|
||||||
toast.error(t("toasts.conversionError"));
|
toast.error("An unknown error occurred during conversion.");
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setConvertingIndex(null);
|
setConvertingIndex(null);
|
||||||
@@ -306,7 +304,7 @@ export function ImageConverter() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleApplySettings = () => {
|
const handleApplySettings = () => {
|
||||||
toast.info(t("toasts.settingsApplied"));
|
toast.info("Settings updated and will be used for all downloads.");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAspectRatioChange = (value: string) => {
|
const handleAspectRatioChange = (value: string) => {
|
||||||
@@ -354,16 +352,16 @@ export function ImageConverter() {
|
|||||||
|
|
||||||
const handleApplyDefaultBaseNameToAll = () => {
|
const handleApplyDefaultBaseNameToAll = () => {
|
||||||
if (!defaultBaseName) {
|
if (!defaultBaseName) {
|
||||||
toast.error(t("toasts.noDefaultBaseName"));
|
toast.error("Please enter a default base name to apply.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!hasImages) {
|
if (!hasImages) {
|
||||||
toast.info(t("toasts.uploadFirst"));
|
toast.info("Upload some images first.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newFilenames = filenames.map(() => defaultBaseName);
|
const newFilenames = filenames.map(() => defaultBaseName);
|
||||||
setFilenames(newFilenames);
|
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 (
|
return (
|
||||||
@@ -374,9 +372,9 @@ export function ImageConverter() {
|
|||||||
<AccordionItem value="image-settings" className="border rounded-lg bg-card">
|
<AccordionItem value="image-settings" className="border rounded-lg bg-card">
|
||||||
<AccordionTrigger className="p-6 hover:no-underline">
|
<AccordionTrigger className="p-6 hover:no-underline">
|
||||||
<div className="text-left">
|
<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">
|
<p className="text-sm text-muted-foreground mt-1">
|
||||||
{t("settings.image.description")}
|
Adjust resolution and scaling for all images.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
@@ -384,13 +382,13 @@ export function ImageConverter() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="flex items-center gap-1.5">
|
<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>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.image.aspectRatioTooltip")}</p>
|
<p>Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -401,7 +399,7 @@ export function ImageConverter() {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{aspectRatios.map((ratio) => (
|
{aspectRatios.map((ratio) => (
|
||||||
<SelectItem key={ratio.value} value={ratio.value}>
|
<SelectItem key={ratio.value} value={ratio.value}>
|
||||||
{t(`settings.image.aspectRatios.${ratio.name}`)}
|
{ratio.name}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -410,13 +408,13 @@ export function ImageConverter() {
|
|||||||
<div className="flex items-end gap-2">
|
<div className="flex items-end gap-2">
|
||||||
<div className="space-y-2 flex-1">
|
<div className="space-y-2 flex-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Label htmlFor="width">{t("settings.image.width")}</Label>
|
<Label htmlFor="width">Width (px)</Label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.image.widthTooltip")}</p>
|
<p>Set the output width in pixels. Leave blank to use the original width.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -429,18 +427,18 @@ export function ImageConverter() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.image.swapDimensionsTooltip")}</p>
|
<p>Swap the entered width and height values.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<div className="space-y-2 flex-1">
|
<div className="space-y-2 flex-1">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Label htmlFor="height">{t("settings.image.height")}</Label>
|
<Label htmlFor="height">Height (px)</Label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.image.heightTooltip")}</p>
|
<p>Set the output height in pixels. Leave blank to use the original height.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -450,13 +448,13 @@ export function ImageConverter() {
|
|||||||
<div className="flex items-center space-x-2 pt-2">
|
<div className="flex items-center space-x-2 pt-2">
|
||||||
<Checkbox id="keep-orientation" checked={keepOrientation} onCheckedChange={(checked) => setKeepOrientation(Boolean(checked))} />
|
<Checkbox id="keep-orientation" checked={keepOrientation} onCheckedChange={(checked) => setKeepOrientation(Boolean(checked))} />
|
||||||
<Label htmlFor="keep-orientation" className="cursor-pointer flex items-center gap-1.5">
|
<Label htmlFor="keep-orientation" className="cursor-pointer flex items-center gap-1.5">
|
||||||
{t("settings.image.keepOrientation")}
|
Keep original orientation
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger onClick={(e) => e.preventDefault()}>
|
<TooltipTrigger onClick={(e) => e.preventDefault()}>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.image.keepOrientationTooltip")}</p>
|
<p>Automatically swaps width and height to match the original image's orientation.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
@@ -464,35 +462,35 @@ export function ImageConverter() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-4 space-y-2">
|
<div className="mt-4 space-y-2">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Label htmlFor="scale-mode">{t("settings.image.scaling")}</Label>
|
<Label htmlFor="scale-mode">Scaling</Label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.image.scalingTooltip")}</p>
|
<p>Determines how the image fits into the new dimensions.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Select value={scaleMode} onValueChange={(value: 'fill' | 'cover' | 'contain') => setScaleMode(value)}>
|
<Select value={scaleMode} onValueChange={(value: 'fill' | 'cover' | 'contain') => setScaleMode(value)}>
|
||||||
<SelectTrigger id="scale-mode"><SelectValue placeholder="Select scaling mode" /></SelectTrigger>
|
<SelectTrigger id="scale-mode"><SelectValue placeholder="Select scaling mode" /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="fill">{t("settings.image.scalingOptions.fill")}</SelectItem>
|
<SelectItem value="fill">Fill (stretch to fit)</SelectItem>
|
||||||
<SelectItem value="cover">{t("settings.image.scalingOptions.cover")}</SelectItem>
|
<SelectItem value="cover">Cover (crop to fit)</SelectItem>
|
||||||
<SelectItem value="contain">{t("settings.image.scalingOptions.contain")}</SelectItem>
|
<SelectItem value="contain">Contain (letterbox)</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
{scaleMode !== 'fill' && (
|
{scaleMode !== 'fill' && (
|
||||||
<div className="mt-4 space-y-2">
|
<div className="mt-4 space-y-2">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Label>{t("settings.image.position")}</Label>
|
<Label>Position</Label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.image.positionTooltip")}</p>
|
<p>Sets the anchor point for 'Cover' or 'Contain' scaling.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -505,8 +503,8 @@ export function ImageConverter() {
|
|||||||
<AccordionItem value="filename-settings" className="border rounded-lg bg-card">
|
<AccordionItem value="filename-settings" className="border rounded-lg bg-card">
|
||||||
<AccordionTrigger className="p-6 hover:no-underline">
|
<AccordionTrigger className="p-6 hover:no-underline">
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h3 className="text-lg font-medium leading-none">{t("settings.filename.title")}</h3>
|
<h3 className="text-lg font-medium leading-none">Filename Settings</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">{t("settings.filename.description")}</p>
|
<p className="text-sm text-muted-foreground mt-1">Customize the output filenames.</p>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="px-6 pb-6">
|
<AccordionContent className="px-6 pb-6">
|
||||||
@@ -514,20 +512,20 @@ export function ImageConverter() {
|
|||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<Switch id="use-default-base-name" checked={useDefaultBaseName} onCheckedChange={setUseDefaultBaseName} />
|
<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">
|
<Label htmlFor="use-default-base-name" className="flex items-center gap-1.5 cursor-pointer">
|
||||||
{t("settings.filename.useDefaultBaseName")}
|
Use default base name
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger onClick={(e) => e.preventDefault()}>
|
<TooltipTrigger onClick={(e) => e.preventDefault()}>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.filename.useDefaultBaseNameTooltip")}</p>
|
<p>When enabled, all newly uploaded images will use the specified default base name.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
</div>
|
</div>
|
||||||
{useDefaultBaseName && (
|
{useDefaultBaseName && (
|
||||||
<div className="space-y-2">
|
<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">
|
<div className="flex items-center gap-2">
|
||||||
<Input
|
<Input
|
||||||
id="default-base-name"
|
id="default-base-name"
|
||||||
@@ -538,11 +536,11 @@ export function ImageConverter() {
|
|||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button variant="outline" size="sm" onClick={handleApplyDefaultBaseNameToAll} disabled={!defaultBaseName || !hasImages}>
|
<Button variant="outline" size="sm" onClick={handleApplyDefaultBaseNameToAll} disabled={!defaultBaseName || !hasImages}>
|
||||||
{t("settings.filename.applyToAll")}
|
Apply to all
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.filename.applyToAllTooltip")}</p>
|
<p>Apply this base name to all currently uploaded images.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -550,13 +548,13 @@ export function ImageConverter() {
|
|||||||
)}
|
)}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Label htmlFor="prefix">{t("settings.filename.prefix")}</Label>
|
<Label htmlFor="prefix">Prefix</Label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.filename.prefixTooltip")}</p>
|
<p>Add text to the beginning of every filename.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -564,13 +562,13 @@ export function ImageConverter() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Label htmlFor="suffix">{t("settings.filename.suffix")}</Label>
|
<Label htmlFor="suffix">Suffix</Label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.filename.suffixTooltip")}</p>
|
<p>Add text to the end of every filename (before the number).</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -579,13 +577,13 @@ export function ImageConverter() {
|
|||||||
<div className="flex items-center space-x-2 pt-2">
|
<div className="flex items-center space-x-2 pt-2">
|
||||||
<Switch id="use-counter" checked={useCounter} onCheckedChange={setUseCounter} />
|
<Switch id="use-counter" checked={useCounter} onCheckedChange={setUseCounter} />
|
||||||
<Label htmlFor="use-counter" className="flex items-center gap-1.5 cursor-pointer">
|
<Label htmlFor="use-counter" className="flex items-center gap-1.5 cursor-pointer">
|
||||||
{t("settings.filename.addSequentialNumber")}
|
Add sequential number
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger onClick={(e) => e.preventDefault()}>
|
<TooltipTrigger onClick={(e) => e.preventDefault()}>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.filename.addSequentialNumberTooltip")}</p>
|
<p>Append a numbered sequence to each filename.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
@@ -594,13 +592,13 @@ export function ImageConverter() {
|
|||||||
<div className="grid grid-cols-2 gap-4 pt-2">
|
<div className="grid grid-cols-2 gap-4 pt-2">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5">
|
<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>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.filename.startNumberTooltip")}</p>
|
<p>The first number to use in the sequence.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -614,13 +612,13 @@ export function ImageConverter() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5">
|
<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>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<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>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -641,21 +639,21 @@ export function ImageConverter() {
|
|||||||
<AccordionItem value="quality-settings" className="border rounded-lg bg-card">
|
<AccordionItem value="quality-settings" className="border rounded-lg bg-card">
|
||||||
<AccordionTrigger className="p-6 hover:no-underline">
|
<AccordionTrigger className="p-6 hover:no-underline">
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<h3 className="text-lg font-medium leading-none">{t("settings.quality.title")}</h3>
|
<h3 className="text-lg font-medium leading-none">Quality Settings</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">{t("settings.quality.description")}</p>
|
<p className="text-sm text-muted-foreground mt-1">Choose format and compression level.</p>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
<AccordionContent className="px-6 pb-6">
|
<AccordionContent className="px-6 pb-6">
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Label htmlFor="format">{t("settings.quality.format")}</Label>
|
<Label htmlFor="format">Format</Label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.quality.formatTooltip")}</p>
|
<p>Choose the output file format for the images.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -671,13 +669,13 @@ export function ImageConverter() {
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<Label htmlFor="quality">{t("settings.quality.quality")}</Label>
|
<Label htmlFor="quality">Quality</Label>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger>
|
<TooltipTrigger>
|
||||||
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
<HelpCircle className="h-4 w-4 text-muted-foreground" />
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("settings.quality.qualityTooltip")}</p>
|
<p>Set compression quality for JPEG/WEBP. Higher is better quality but larger file size.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -693,7 +691,7 @@ export function ImageConverter() {
|
|||||||
disabled={format === 'png'}
|
disabled={format === 'png'}
|
||||||
/>
|
/>
|
||||||
{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>
|
||||||
</div>
|
</div>
|
||||||
@@ -708,11 +706,11 @@ export function ImageConverter() {
|
|||||||
className="w-full"
|
className="w-full"
|
||||||
>
|
>
|
||||||
<Check className="mr-2 h-4 w-4" />
|
<Check className="mr-2 h-4 w-4" />
|
||||||
{t("applySettings")}
|
Apply Settings
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("applySettingsTooltip")}</p>
|
<p>Confirm and apply all the settings above. This does not download the images.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -721,7 +719,7 @@ export function ImageConverter() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardContent className="pt-6">
|
<CardContent className="pt-6">
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<h3 className="text-lg font-medium">{t("uploadTitle")}</h3>
|
<h3 className="text-lg font-medium">Upload Images</h3>
|
||||||
<div
|
<div
|
||||||
className={cn(
|
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",
|
"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">
|
<div className="flex flex-col items-center justify-center text-center text-muted-foreground">
|
||||||
<Upload className="w-8 h-8 mb-2" />
|
<Upload className="w-8 h-8 mb-2" />
|
||||||
<p className="font-semibold">{t("uploadButton")}</p>
|
<p className="font-semibold">Click or drag and drop to upload</p>
|
||||||
<p className="text-xs text-muted-foreground mt-1">{t("uploadHint")}</p>
|
<p className="text-xs text-muted-foreground mt-1">PNG, JPG, WEBP supported</p>
|
||||||
</div>
|
</div>
|
||||||
<Input type="file" ref={fileInputRef} onChange={handleImageChange} className="hidden" accept="image/*" multiple />
|
<Input type="file" ref={fileInputRef} onChange={handleImageChange} className="hidden" accept="image/*" multiple />
|
||||||
</div>
|
</div>
|
||||||
@@ -747,25 +745,25 @@ export function ImageConverter() {
|
|||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<CardTitle>{t("uploadedImagesTitle")}</CardTitle>
|
<CardTitle>Uploaded Images</CardTitle>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<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>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("clearAllTooltip")}</p>
|
<p>Remove all uploaded images.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<Button onClick={handleConvertAndDownloadAll} disabled={!hasImages || isConverting || convertingIndex !== null}>
|
<Button onClick={handleConvertAndDownloadAll} disabled={!hasImages || isConverting || convertingIndex !== null}>
|
||||||
<Download className="mr-2 h-4 w-4" />
|
<Download className="mr-2 h-4 w-4" />
|
||||||
{isConverting ? t("downloadAllConverting") : t("downloadAll", { count: images.length })}
|
{isConverting ? "Converting..." : `Download All (${images.length})`}
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("downloadAllTooltip")}</p>
|
<p>Convert and download all images with the current settings.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -777,20 +775,19 @@ export function ImageConverter() {
|
|||||||
const baseFilename = generateFinalFilename(index);
|
const baseFilename = generateFinalFilename(index);
|
||||||
const dimensionSuffix = width && height ? `_${width}x${height}` : '';
|
const dimensionSuffix = width && height ? `_${width}x${height}` : '';
|
||||||
const finalFilename = `${baseFilename}${dimensionSuffix}`;
|
const finalFilename = `${baseFilename}${dimensionSuffix}`;
|
||||||
const finalFilenameWithExt = `${finalFilename}.${format}`;
|
|
||||||
return (
|
return (
|
||||||
<div key={url} className="p-4 border rounded-lg flex items-center gap-4">
|
<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" />
|
<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">
|
<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
|
<Input
|
||||||
id={`filename-${index}`}
|
id={`filename-${index}`}
|
||||||
value={filenames[index]}
|
value={filenames[index]}
|
||||||
onChange={(e) => handleFilenameChange(index, e.target.value)}
|
onChange={(e) => handleFilenameChange(index, e.target.value)}
|
||||||
className="text-sm font-medium h-8 mt-1"
|
className="text-sm font-medium h-8 mt-1"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-muted-foreground truncate mt-1" title={finalFilenameWithExt}>
|
<p className="text-xs text-muted-foreground truncate mt-1" title={`${finalFilename}.${format}`}>
|
||||||
{t("finalNameLabel", { filename: finalFilenameWithExt })}
|
Final name: {finalFilename}.{format}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center shrink-0">
|
<div className="flex items-center shrink-0">
|
||||||
@@ -807,7 +804,7 @@ export function ImageConverter() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("downloadImageTooltip")}</p>
|
<p>Download this image</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -823,7 +820,7 @@ export function ImageConverter() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("removeImageTooltip")}</p>
|
<p>Remove this image</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import {getRequestConfig} from 'next-intl/server';
|
|
||||||
|
|
||||||
export default getRequestConfig(async ({locale}) => ({
|
|
||||||
messages: (await import(`../messages/${locale}.json`)).default
|
|
||||||
}));
|
|
||||||
Reference in New Issue
Block a user