Reverted all changes back to version 92d31b0051
This commit is contained in:
152
messages/de.json
152
messages/de.json
@@ -1,152 +0,0 @@
|
|||||||
{
|
|
||||||
"Metadata": {
|
|
||||||
"title": "Bild-Web-Exporter",
|
|
||||||
"description": "Laden Sie ein Bild hoch und exportieren Sie es in einer anderen Auflösung und einem anderen Format."
|
|
||||||
},
|
|
||||||
"HomePage": {
|
|
||||||
"title": "Bild-Web-Exporter",
|
|
||||||
"subtitle": "Laden Sie ein Bild hoch und exportieren Sie es in einer anderen Auflösung und einem anderen Format."
|
|
||||||
},
|
|
||||||
"Footer": {
|
|
||||||
"copyright": "© {year} Pascal Linxweiler",
|
|
||||||
"imprint": "Impressum",
|
|
||||||
"privacy": "Datenschutz",
|
|
||||||
"language": "Sprache"
|
|
||||||
},
|
|
||||||
"LanguageSwitcher": {
|
|
||||||
"en": "Englisch",
|
|
||||||
"de": "Deutsch"
|
|
||||||
},
|
|
||||||
"ChangelogPage": {
|
|
||||||
"backToConverter": "Zurück zum Konverter",
|
|
||||||
"title": "Änderungsprotokoll",
|
|
||||||
"subtitle": "Verfolgung aller neuen Funktionen, Verbesserungen und Fehlerbehebungen."
|
|
||||||
},
|
|
||||||
"Changelog": {
|
|
||||||
"version": "Version",
|
|
||||||
"new": "Neu",
|
|
||||||
"improved": "Verbessert",
|
|
||||||
"fixed": "Behoben"
|
|
||||||
},
|
|
||||||
"ImprintPage": {
|
|
||||||
"backToConverter": "Zurück zum Konverter",
|
|
||||||
"title": "Impressum",
|
|
||||||
"tmgInfo": "Angaben gemäß § 5 TMG",
|
|
||||||
"contactInfoTitle": "Kontaktinformationen:",
|
|
||||||
"contactName": "[Ihr Firmenname]",
|
|
||||||
"contactStreet": "[Straße & Hausnummer]",
|
|
||||||
"contactCity": "[PLZ & Stadt]",
|
|
||||||
"contactEmail": "E-Mail: [ihre-email@beispiel.com]",
|
|
||||||
"contactPhone": "Telefon: [ihre-telefonnummer]",
|
|
||||||
"representedByTitle": "Vertreten durch:",
|
|
||||||
"representedByName": "[Ihr Name/Name des Geschäftsführers]",
|
|
||||||
"disclaimerTitle": "Haftungsausschluss:",
|
|
||||||
"disclaimerText": "Dies ist ein Musterimpressum und nicht rechtsverbindlich. Bitte ersetzen Sie den Platzhalterinhalt durch Ihre eigenen Informationen und konsultieren Sie einen Rechtsexperten, um die Einhaltung aller geltenden Gesetze sicherzustellen."
|
|
||||||
},
|
|
||||||
"PrivacyPage": {
|
|
||||||
"backToConverter": "Zurück zum Konverter",
|
|
||||||
"title": "Datenschutzerklärung",
|
|
||||||
"generalInfoTitle": "1. Allgemeine Informationen",
|
|
||||||
"generalInfoText": "Dies ist ein Platzhalter für Ihre Datenschutzerklärung. Sie beschreibt, wie personenbezogene Daten erfasst, verwendet und geschützt werden, wenn Sie diese Website nutzen.",
|
|
||||||
"dataCollectionTitle": "2. Datenerfassung auf dieser Website",
|
|
||||||
"dataCollectionText": "Die gesamte Bildverarbeitung findet direkt in Ihrem Browser statt. Die von Ihnen hochgeladenen Bilder werden nicht an einen Server gesendet und von uns nicht gespeichert. Wir erheben keine personenbezogenen Daten aus den Bildern.",
|
|
||||||
"yourRightsTitle": "3. Ihre Rechte",
|
|
||||||
"yourRightsText": "Da keine personenbezogenen Daten erhoben werden, sind Rechte bezüglich Auskunft, Berichtigung oder Löschung personenbezogener Daten in diesem Zusammenhang nicht anwendbar.",
|
|
||||||
"disclaimerTitle": "Haftungsausschluss:",
|
|
||||||
"disclaimerText": "Dies ist eine Muster-Datenschutzerklärung und nicht rechtsverbindlich. Es ist entscheidend, diesen Text an Ihre spezifischen Datenverarbeitungsaktivitäten anzupassen und einen Rechtsexperten zu konsultieren, um die vollständige DSGVO-Konformität sicherzustellen."
|
|
||||||
},
|
|
||||||
"ImageConverter": {
|
|
||||||
"uploadTitle": "Bilder hochladen",
|
|
||||||
"uploadPrompt": "Klicken oder ziehen Sie Dateien hierher, um sie hochzuladen",
|
|
||||||
"uploadHint": "PNG, JPG, WEBP werden unterstützt",
|
|
||||||
"uploadedImagesTitle": "Hochgeladene Bilder",
|
|
||||||
"clearAll": "Alle löschen",
|
|
||||||
"clearAllTooltip": "Alle hochgeladenen Bilder entfernen.",
|
|
||||||
"downloadAll": "Alle ({count}) herunterladen",
|
|
||||||
"downloadAllConverting": "Konvertiere...",
|
|
||||||
"downloadAllTooltip": "Alle Bilder mit den aktuellen Einstellungen konvertieren und herunterladen.",
|
|
||||||
"baseNameLabel": "Basisname",
|
|
||||||
"finalNameLabel": "Endgültiger Name: {filename}",
|
|
||||||
"downloadSingleTooltip": "Dieses Bild herunterladen",
|
|
||||||
"removeSingleTooltip": "Dieses Bild entfernen",
|
|
||||||
"imageSettingsTitle": "Bildeinstellungen",
|
|
||||||
"imageSettingsSubtitle": "Passen Sie Auflösung und Skalierung für alle Bilder an.",
|
|
||||||
"aspectRatioLabel": "Seitenverhältnis",
|
|
||||||
"aspectRatioTooltip": "Wählen Sie ein voreingestelltes Seitenverhältnis oder 'Benutzerdefiniert', um die Abmessungen manuell einzugeben.",
|
|
||||||
"selectAspectRatio": "Seitenverhältnis auswählen",
|
|
||||||
"aspectRatios": {
|
|
||||||
"custom": "Benutzerdefiniert",
|
|
||||||
"square": "1:1 (Quadratisch)",
|
|
||||||
"standard": "4:3 (Standard)",
|
|
||||||
"photography": "3:2 (Fotografie)",
|
|
||||||
"widescreen": "16:9 (Breitbild)"
|
|
||||||
},
|
|
||||||
"widthLabel": "Breite (px)",
|
|
||||||
"widthTooltip": "Legen Sie die Ausgabebreite in Pixel fest. Leer lassen, um die Originalbreite zu verwenden.",
|
|
||||||
"widthPlaceholder": "Auto",
|
|
||||||
"swapDimensionsTooltip": "Tauschen Sie die eingegebenen Breiten- und Höhenwerte.",
|
|
||||||
"heightLabel": "Höhe (px)",
|
|
||||||
"heightTooltip": "Legen Sie die Ausgabehöhe in Pixel fest. Leer lassen, um die Originalhöhe zu verwenden.",
|
|
||||||
"heightPlaceholder": "Auto",
|
|
||||||
"keepOrientationLabel": "Originalausrichtung beibehalten",
|
|
||||||
"keepOrientationTooltip": "Tauscht automatisch Breite und Höhe, um der Ausrichtung des Originalbildes zu entsprechen.",
|
|
||||||
"scalingLabel": "Skalierung",
|
|
||||||
"scalingTooltip": "Bestimmt, wie das Bild in die neuen Abmessungen passt.",
|
|
||||||
"selectScaling": "Skalierungsmodus auswählen",
|
|
||||||
"scalingOptions": {
|
|
||||||
"fill": "Füllen (strecken)",
|
|
||||||
"cover": "Abdecken (zuschneiden)",
|
|
||||||
"contain": "Enthalten (Letterbox)"
|
|
||||||
},
|
|
||||||
"positionLabel": "Position",
|
|
||||||
"positionTooltip": "Legt den Ankerpunkt für die Skalierung 'Abdecken' oder 'Enthalten' fest.",
|
|
||||||
"filenameSettingsTitle": "Dateinameneinstellungen",
|
|
||||||
"filenameSettingsSubtitle": "Passen Sie die Ausgabedateinamen an.",
|
|
||||||
"useDefaultBaseNameLabel": "Standard-Basisnamen verwenden",
|
|
||||||
"useDefaultBaseNameTooltip": "Wenn aktiviert, verwenden alle neu hochgeladenen Bilder den angegebenen Standard-Basisnamen.",
|
|
||||||
"defaultBaseNameLabel": "Standard-Basisname",
|
|
||||||
"defaultBaseNamePlaceholder": "z.B. new-york-reise",
|
|
||||||
"applyToAll": "Auf alle anwenden",
|
|
||||||
"applyToAllTooltip": "Diesen Basisnamen auf alle aktuell hochgeladenen Bilder anwenden.",
|
|
||||||
"prefixLabel": "Präfix",
|
|
||||||
"prefixTooltip": "Fügen Sie Text am Anfang jedes Dateinamens hinzu.",
|
|
||||||
"prefixPlaceholder": "z.B. reise-",
|
|
||||||
"suffixLabel": "Suffix",
|
|
||||||
"suffixTooltip": "Fügen Sie Text am Ende jedes Dateinamens hinzu (vor der Nummer).",
|
|
||||||
"suffixPlaceholder": "z.B. -bearbeitet",
|
|
||||||
"useCounterLabel": "Laufende Nummer hinzufügen",
|
|
||||||
"useCounterTooltip": "Fügen Sie jedem Dateinamen eine nummerierte Sequenz an.",
|
|
||||||
"counterStartLabel": "Startnummer",
|
|
||||||
"counterStartTooltip": "Die erste Nummer, die in der Sequenz verwendet wird.",
|
|
||||||
"counterDigitsLabel": "Auffüllziffern",
|
|
||||||
"counterDigitsTooltip": "Gesamtzahl der Ziffern für den Zähler, aufgefüllt mit führenden Nullen (z.B. 3 für 001).",
|
|
||||||
"qualitySettingsTitle": "Qualitätseinstellungen",
|
|
||||||
"qualitySettingsSubtitle": "Wählen Sie Format und Komprimierungsstufe.",
|
|
||||||
"formatLabel": "Format",
|
|
||||||
"formatTooltip": "Wählen Sie das Ausgabedateiformat für die Bilder.",
|
|
||||||
"selectFormat": "Format auswählen",
|
|
||||||
"qualityLabel": "Qualität",
|
|
||||||
"qualityTooltip": "Stellen Sie die Komprimierungsqualität für JPEG/WEBP ein. Höher bedeutet bessere Qualität, aber größere Dateigröße.",
|
|
||||||
"qualityDisabledHint": "Der Qualitätsschieberegler ist für PNG (verlustfreies Format) deaktiviert.",
|
|
||||||
"resetButton": "Zurücksetzen",
|
|
||||||
"resetButtonTooltip": "Alle Einstellungen auf ihre Standardwerte zurücksetzen.",
|
|
||||||
"applyButton": "Anwenden",
|
|
||||||
"applyButtonTooltip": "Bestätigen und alle oben genannten Einstellungen anwenden. Dies lädt die Bilder nicht herunter.",
|
|
||||||
"toasts": {
|
|
||||||
"noImages": "Keine gültigen Bilddateien gefunden.",
|
|
||||||
"imagesAdded": "{count, plural, one {# Bild hinzugefügt.} other {# Bilder hinzugefügt.}}",
|
|
||||||
"allCleared": "Alle Bilder gelöscht.",
|
|
||||||
"conversionStarting": "Konvertierung für {count, plural, one {# Bild wird gestartet...} other {# 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.",
|
|
||||||
"settingsReset": "Alle Einstellungen wurden auf ihre Standardwerte zurückgesetzt.",
|
|
||||||
"defaultBaseNameMissing": "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 \"{name}\" gesetzt.",
|
|
||||||
"uploadError": "Bitte laden Sie zuerst Bilder hoch."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
152
messages/en.json
152
messages/en.json
@@ -1,152 +0,0 @@
|
|||||||
{
|
|
||||||
"Metadata": {
|
|
||||||
"title": "Image Web Exporter",
|
|
||||||
"description": "Upload a picture, then export it in a different resolution and format."
|
|
||||||
},
|
|
||||||
"HomePage": {
|
|
||||||
"title": "Image Web Exporter",
|
|
||||||
"subtitle": "Upload a picture, then export it in a different resolution and format."
|
|
||||||
},
|
|
||||||
"Footer": {
|
|
||||||
"copyright": "© {year} Pascal Linxweiler",
|
|
||||||
"imprint": "Imprint",
|
|
||||||
"privacy": "Privacy",
|
|
||||||
"language": "Language"
|
|
||||||
},
|
|
||||||
"LanguageSwitcher": {
|
|
||||||
"en": "English",
|
|
||||||
"de": "German"
|
|
||||||
},
|
|
||||||
"ChangelogPage": {
|
|
||||||
"backToConverter": "Back to Converter",
|
|
||||||
"title": "Changelog",
|
|
||||||
"subtitle": "Tracking all the new features, improvements, and bug fixes."
|
|
||||||
},
|
|
||||||
"Changelog": {
|
|
||||||
"version": "Version",
|
|
||||||
"new": "New",
|
|
||||||
"improved": "Improved",
|
|
||||||
"fixed": "Fixed"
|
|
||||||
},
|
|
||||||
"ImprintPage": {
|
|
||||||
"backToConverter": "Back to Converter",
|
|
||||||
"title": "Imprint",
|
|
||||||
"tmgInfo": "Information according to § 5 TMG (German Telemedia Act)",
|
|
||||||
"contactInfoTitle": "Contact Information:",
|
|
||||||
"contactName": "[Your Company Name]",
|
|
||||||
"contactStreet": "[Street Name & Number]",
|
|
||||||
"contactCity": "[Postal Code & City]",
|
|
||||||
"contactEmail": "Email: [your-email@example.com]",
|
|
||||||
"contactPhone": "Phone: [your-phone-number]",
|
|
||||||
"representedByTitle": "Represented by:",
|
|
||||||
"representedByName": "[Your Name/CEO's Name]",
|
|
||||||
"disclaimerTitle": "Disclaimer:",
|
|
||||||
"disclaimerText": "This is a sample imprint and not legally binding. Please replace the placeholder content with your own information and consult a legal professional to ensure compliance with all applicable laws."
|
|
||||||
},
|
|
||||||
"PrivacyPage": {
|
|
||||||
"backToConverter": "Back to Converter",
|
|
||||||
"title": "Data Privacy Policy",
|
|
||||||
"generalInfoTitle": "1. General Information",
|
|
||||||
"generalInfoText": "This is a placeholder for your data privacy policy. It outlines how personal data is collected, used, and protected when you use this website.",
|
|
||||||
"dataCollectionTitle": "2. Data Collection on This Website",
|
|
||||||
"dataCollectionText": "All image processing happens directly in your browser. The images you upload are not sent to any server and are not stored by us. We do not collect any personal data from the images.",
|
|
||||||
"yourRightsTitle": "3. Your Rights",
|
|
||||||
"yourRightsText": "As no personal data is collected, rights regarding access, rectification, or erasure of personal data are not applicable in this context.",
|
|
||||||
"disclaimerTitle": "Disclaimer:",
|
|
||||||
"disclaimerText": "This is a sample privacy policy and not legally binding. It is crucial to adapt this text to your specific data processing activities and to consult with a legal professional to ensure full GDPR compliance."
|
|
||||||
},
|
|
||||||
"ImageConverter": {
|
|
||||||
"uploadTitle": "Upload Images",
|
|
||||||
"uploadPrompt": "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}",
|
|
||||||
"downloadSingleTooltip": "Download this image",
|
|
||||||
"removeSingleTooltip": "Remove this image",
|
|
||||||
"imageSettingsTitle": "Image Settings",
|
|
||||||
"imageSettingsSubtitle": "Adjust resolution and scaling for all images.",
|
|
||||||
"aspectRatioLabel": "Aspect Ratio",
|
|
||||||
"aspectRatioTooltip": "Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.",
|
|
||||||
"selectAspectRatio": "Select aspect ratio",
|
|
||||||
"aspectRatios": {
|
|
||||||
"custom": "Custom",
|
|
||||||
"square": "1:1 (Square)",
|
|
||||||
"standard": "4:3 (Standard)",
|
|
||||||
"photography": "3:2 (Photography)",
|
|
||||||
"widescreen": "16:9 (Widescreen)"
|
|
||||||
},
|
|
||||||
"widthLabel": "Width (px)",
|
|
||||||
"widthTooltip": "Set the output width in pixels. Leave blank to use the original width.",
|
|
||||||
"widthPlaceholder": "Auto",
|
|
||||||
"swapDimensionsTooltip": "Swap the entered width and height values.",
|
|
||||||
"heightLabel": "Height (px)",
|
|
||||||
"heightTooltip": "Set the output height in pixels. Leave blank to use the original height.",
|
|
||||||
"heightPlaceholder": "Auto",
|
|
||||||
"keepOrientationLabel": "Keep original orientation",
|
|
||||||
"keepOrientationTooltip": "Automatically swaps width and height to match the original image's orientation.",
|
|
||||||
"scalingLabel": "Scaling",
|
|
||||||
"scalingTooltip": "Determines how the image fits into the new dimensions.",
|
|
||||||
"selectScaling": "Select scaling mode",
|
|
||||||
"scalingOptions": {
|
|
||||||
"fill": "Fill (stretch to fit)",
|
|
||||||
"cover": "Cover (crop to fit)",
|
|
||||||
"contain": "Contain (letterbox)"
|
|
||||||
},
|
|
||||||
"positionLabel": "Position",
|
|
||||||
"positionTooltip": "Sets the anchor point for 'Cover' or 'Contain' scaling.",
|
|
||||||
"filenameSettingsTitle": "Filename Settings",
|
|
||||||
"filenameSettingsSubtitle": "Customize the output filenames.",
|
|
||||||
"useDefaultBaseNameLabel": "Use default base name",
|
|
||||||
"useDefaultBaseNameTooltip": "When enabled, all newly uploaded images will use the specified default base name.",
|
|
||||||
"defaultBaseNameLabel": "Default base name",
|
|
||||||
"defaultBaseNamePlaceholder": "e.g., new-york-trip",
|
|
||||||
"applyToAll": "Apply to all",
|
|
||||||
"applyToAllTooltip": "Apply this base name to all currently uploaded images.",
|
|
||||||
"prefixLabel": "Prefix",
|
|
||||||
"prefixTooltip": "Add text to the beginning of every filename.",
|
|
||||||
"prefixPlaceholder": "e.g., travel-",
|
|
||||||
"suffixLabel": "Suffix",
|
|
||||||
"suffixTooltip": "Add text to the end of every filename (before the number).",
|
|
||||||
"suffixPlaceholder": "e.g., -edit",
|
|
||||||
"useCounterLabel": "Add sequential number",
|
|
||||||
"useCounterTooltip": "Append a numbered sequence to each filename.",
|
|
||||||
"counterStartLabel": "Start number",
|
|
||||||
"counterStartTooltip": "The first number to use in the sequence.",
|
|
||||||
"counterDigitsLabel": "Padding digits",
|
|
||||||
"counterDigitsTooltip": "Total number of digits for the counter, padded with leading zeros (e.g., 3 for 001).",
|
|
||||||
"qualitySettingsTitle": "Quality Settings",
|
|
||||||
"qualitySettingsSubtitle": "Choose format and compression level.",
|
|
||||||
"formatLabel": "Format",
|
|
||||||
"formatTooltip": "Choose the output file format for the images.",
|
|
||||||
"selectFormat": "Select format",
|
|
||||||
"qualityLabel": "Quality",
|
|
||||||
"qualityTooltip": "Set compression quality for JPEG/WEBP. Higher is better quality but larger file size.",
|
|
||||||
"qualityDisabledHint": "Quality slider is disabled for PNG (lossless format).",
|
|
||||||
"resetButton": "Reset",
|
|
||||||
"resetButtonTooltip": "Reset all settings to their default values.",
|
|
||||||
"applyButton": "Apply",
|
|
||||||
"applyButtonTooltip": "Confirm and apply all the settings above. This does not download the images.",
|
|
||||||
"toasts": {
|
|
||||||
"noImages": "No valid image files found.",
|
|
||||||
"imagesAdded": "{count, plural, one {# image added.} other {# images added.}}",
|
|
||||||
"allCleared": "All images cleared.",
|
|
||||||
"conversionStarting": "Starting conversion for {count, plural, one {# image...} other {# 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.",
|
|
||||||
"settingsReset": "All settings have been reset to their defaults.",
|
|
||||||
"defaultBaseNameMissing": "Please enter a default base name to apply.",
|
|
||||||
"uploadFirst": "Upload some images first.",
|
|
||||||
"baseNameApplied": "Set base name to \"{name}\" for all {count} images.",
|
|
||||||
"uploadError": "Please upload images first."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,47 +0,0 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ArrowLeft } from "lucide-react";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
|
|
||||||
export default async function ImprintPage() {
|
|
||||||
const t = await getTranslations("ImprintPage");
|
|
||||||
return (
|
|
||||||
<div className="relative flex flex-col items-center min-h-screen p-4 sm:p-8 bg-gray-50 dark:bg-background">
|
|
||||||
<div className="w-full max-w-4xl mx-auto">
|
|
||||||
<Button asChild variant="ghost" className="mb-4 -ml-4">
|
|
||||||
<Link href="/">
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
{t("backToConverter")}
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<main className="w-full">
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-3xl font-bold">{t("title")}</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-6 text-muted-foreground">
|
|
||||||
<p>{t("tmgInfo")}</p>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="font-semibold text-foreground">{t("contactInfoTitle")}</h3>
|
|
||||||
<p>{t("contactName")}</p>
|
|
||||||
<p>{t("contactStreet")}</p>
|
|
||||||
<p>{t("contactCity")}</p>
|
|
||||||
<p>{t("contactEmail")}</p>
|
|
||||||
<p>{t("contactPhone")}</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="font-semibold text-foreground">{t("representedByTitle")}</h3>
|
|
||||||
<p>{t("representedByName")}</p>
|
|
||||||
</div>
|
|
||||||
<div className="pt-4 border-t">
|
|
||||||
<h3 className="font-semibold text-foreground">{t("disclaimerTitle")}</h3>
|
|
||||||
<p className="text-sm">{t("disclaimerText")}</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,66 +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";
|
|
||||||
import { Footer } from "@/components/footer";
|
|
||||||
import { NextIntlClientProvider } from "next-intl";
|
|
||||||
import { getMessages } from "next-intl/server";
|
|
||||||
|
|
||||||
const geistSans = Geist({
|
|
||||||
variable: "--font-geist-sans",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const geistMono = Geist_Mono({
|
|
||||||
variable: "--font-geist-mono",
|
|
||||||
subsets: ["latin"],
|
|
||||||
});
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
params: { locale: string };
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function generateMetadata({
|
|
||||||
params: { locale },
|
|
||||||
}: Props): Promise<Metadata> {
|
|
||||||
// Using a dynamic import for messages to avoid bundling all of them
|
|
||||||
const messages = (await import(`../../../messages/${locale}.json`)).default;
|
|
||||||
const t = (key: string) => messages.Metadata[key as keyof typeof messages.Metadata] || key;
|
|
||||||
|
|
||||||
return {
|
|
||||||
title: t("title"),
|
|
||||||
description: t("description"),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default async function RootLayout({
|
|
||||||
children,
|
|
||||||
params: { locale },
|
|
||||||
}: Readonly<{
|
|
||||||
children: React.ReactNode;
|
|
||||||
params: { locale: string };
|
|
||||||
}>) {
|
|
||||||
const messages = await getMessages();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<html lang={locale} suppressHydrationWarning>
|
|
||||||
<body
|
|
||||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
|
||||||
>
|
|
||||||
<NextIntlClientProvider messages={messages}>
|
|
||||||
<ThemeProvider
|
|
||||||
attribute="class"
|
|
||||||
defaultTheme="system"
|
|
||||||
enableSystem
|
|
||||||
disableTransitionOnChange
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<Footer />
|
|
||||||
<Toaster />
|
|
||||||
</ThemeProvider>
|
|
||||||
</NextIntlClientProvider>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import Link from "next/link";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ArrowLeft } from "lucide-react";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
|
|
||||||
export default async function PrivacyPage() {
|
|
||||||
const t = await getTranslations("PrivacyPage");
|
|
||||||
return (
|
|
||||||
<div className="relative flex flex-col items-center min-h-screen p-4 sm:p-8 bg-gray-50 dark:bg-background">
|
|
||||||
<div className="w-full max-w-4xl mx-auto">
|
|
||||||
<Button asChild variant="ghost" className="mb-4 -ml-4">
|
|
||||||
<Link href="/">
|
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
|
||||||
{t("backToConverter")}
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
<main className="w-full">
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="text-3xl font-bold">{t("title")}</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent className="space-y-6 text-muted-foreground">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="font-semibold text-foreground">{t("generalInfoTitle")}</h3>
|
|
||||||
<p>{t("generalInfoText")}</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="font-semibold text-foreground">{t("dataCollectionTitle")}</h3>
|
|
||||||
<p>{t("dataCollectionText")}</p>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<h3 className="font-semibold text-foreground">{t("yourRightsTitle")}</h3>
|
|
||||||
<p>{t("yourRightsText")}</p>
|
|
||||||
</div>
|
|
||||||
<div className="pt-4 border-t">
|
|
||||||
<h3 className="font-semibold text-foreground">{t("disclaimerTitle")}</h3>
|
|
||||||
<p className="text-sm">{t("disclaimerText")}</p>
|
|
||||||
</div>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,15 @@ import { Changelog } from "@/components/changelog";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
|
|
||||||
export default async function ChangelogPage() {
|
export default function ChangelogPage() {
|
||||||
const t = await getTranslations("ChangelogPage");
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-col items-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 min-h-screen p-4 sm:p-8 bg-gray-50 dark:bg-background font-[family-name:var(--font-geist-sans)]">
|
||||||
<div className="w-full max-w-4xl mx-auto">
|
<div className="w-full max-w-4xl mx-auto">
|
||||||
<Button asChild variant="ghost" className="mb-4 -ml-4">
|
<Button asChild variant="ghost" className="mb-4 -ml-4">
|
||||||
<Link href="/">
|
<Link href="/">
|
||||||
<ArrowLeft className="mr-2 h-4 w-4" />
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
{t("backToConverter")}
|
Back to Converter
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
49
src/app/imprint/page.tsx
Normal file
49
src/app/imprint/page.tsx
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
export default function ImprintPage() {
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-col items-center min-h-screen p-4 sm:p-8 bg-gray-50 dark:bg-background">
|
||||||
|
<div className="w-full max-w-4xl mx-auto">
|
||||||
|
<Button asChild variant="ghost" className="mb-4 -ml-4">
|
||||||
|
<Link href="/">
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
Back to Converter
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<main className="w-full">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-3xl font-bold">Imprint</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6 text-muted-foreground">
|
||||||
|
<p>
|
||||||
|
Information according to § 5 TMG (German Telemedia Act)
|
||||||
|
</p>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="font-semibold text-foreground">Contact Information:</h3>
|
||||||
|
<p>[Your Company Name]</p>
|
||||||
|
<p>[Street Name & Number]</p>
|
||||||
|
<p>[Postal Code & City]</p>
|
||||||
|
<p>Email: [your-email@example.com]</p>
|
||||||
|
<p>Phone: [your-phone-number]</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="font-semibold text-foreground">Represented by:</h3>
|
||||||
|
<p>[Your Name/CEO's Name]</p>
|
||||||
|
</div>
|
||||||
|
<div className="pt-4 border-t">
|
||||||
|
<h3 className="font-semibold text-foreground">Disclaimer:</h3>
|
||||||
|
<p className="text-sm">
|
||||||
|
This is a sample imprint and not legally binding. Please replace the placeholder content with your own information and consult a legal professional to ensure compliance with all applicable laws.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
46
src/app/layout.tsx
Normal file
46
src/app/layout.tsx
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
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";
|
||||||
|
import { Footer } from "@/components/footer";
|
||||||
|
|
||||||
|
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}
|
||||||
|
<Footer />
|
||||||
|
<Toaster />
|
||||||
|
</ThemeProvider>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,17 +1,15 @@
|
|||||||
import { ImageConverter } from "@/components/image-converter";
|
import { ImageConverter } from "@/components/image-converter";
|
||||||
import { getTranslations } from "next-intl/server";
|
|
||||||
|
|
||||||
export default async function Home() {
|
export default function Home() {
|
||||||
const t = await getTranslations("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)]">
|
||||||
<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("subtitle")}
|
Upload a picture, then export it in a different resolution and format.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<ImageConverter />
|
<ImageConverter />
|
||||||
52
src/app/privacy/page.tsx
Normal file
52
src/app/privacy/page.tsx
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import Link from "next/link";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { ArrowLeft } from "lucide-react";
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
|
||||||
|
export default function PrivacyPage() {
|
||||||
|
return (
|
||||||
|
<div className="relative flex flex-col items-center min-h-screen p-4 sm:p-8 bg-gray-50 dark:bg-background">
|
||||||
|
<div className="w-full max-w-4xl mx-auto">
|
||||||
|
<Button asChild variant="ghost" className="mb-4 -ml-4">
|
||||||
|
<Link href="/">
|
||||||
|
<ArrowLeft className="mr-2 h-4 w-4" />
|
||||||
|
Back to Converter
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<main className="w-full">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-3xl font-bold">Data Privacy Policy</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-6 text-muted-foreground">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="font-semibold text-foreground">1. General Information</h3>
|
||||||
|
<p>
|
||||||
|
This is a placeholder for your data privacy policy. It outlines how personal data is collected, used, and protected when you use this website.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="font-semibold text-foreground">2. Data Collection on This Website</h3>
|
||||||
|
<p>
|
||||||
|
All image processing happens directly in your browser. The images you upload are not sent to any server and are not stored by us. We do not collect any personal data from the images.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
<h3 className="font-semibold text-foreground">3. Your Rights</h3>
|
||||||
|
<p>
|
||||||
|
As no personal data is collected, rights regarding access, rectification, or erasure of personal data are not applicable in this context.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="pt-4 border-t">
|
||||||
|
<h3 className="font-semibold text-foreground">Disclaimer:</h3>
|
||||||
|
<p className="text-sm">
|
||||||
|
This is a sample privacy policy and not legally binding. It is crucial to adapt this text to your specific data processing activities and to consult with a legal professional to ensure full GDPR compliance.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -4,33 +4,16 @@ import { Badge } from "@/components/ui/badge";
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { changelogData } from "@/lib/changelog-data";
|
import { changelogData } from "@/lib/changelog-data";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
export function Changelog() {
|
export function Changelog() {
|
||||||
const tPage = useTranslations("ChangelogPage");
|
|
||||||
const t = useTranslations("Changelog");
|
|
||||||
|
|
||||||
const getChangeTypeTranslation = (type: string) => {
|
|
||||||
switch (type) {
|
|
||||||
case "New":
|
|
||||||
return t("new");
|
|
||||||
case "Improved":
|
|
||||||
return t("improved");
|
|
||||||
case "Fixed":
|
|
||||||
return t("fixed");
|
|
||||||
default:
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
<div className="w-full max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||||
<div className="text-center mb-12">
|
<div className="text-center mb-12">
|
||||||
<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">
|
||||||
{tPage("title")}
|
Changelog
|
||||||
</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">
|
||||||
{tPage("subtitle")}
|
Tracking all the new features, improvements, and bug fixes.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
@@ -38,7 +21,7 @@ export function Changelog() {
|
|||||||
<Card key={entry.version}>
|
<Card key={entry.version}>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2">
|
||||||
<CardTitle className="text-2xl font-bold">{t("version")} {entry.version}</CardTitle>
|
<CardTitle className="text-2xl font-bold">Version {entry.version}</CardTitle>
|
||||||
<p className="text-sm text-muted-foreground">{entry.date}</p>
|
<p className="text-sm text-muted-foreground">{entry.date}</p>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
@@ -54,7 +37,7 @@ export function Changelog() {
|
|||||||
"border-red-500/50 bg-red-500/10 text-red-700 dark:text-red-300": change.type === "Fixed",
|
"border-red-500/50 bg-red-500/10 text-red-700 dark:text-red-300": change.type === "Fixed",
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{getChangeTypeTranslation(change.type)}
|
{change.type}
|
||||||
</Badge>
|
</Badge>
|
||||||
<p className="text-foreground">{change.text}</p>
|
<p className="text-foreground">{change.text}</p>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@@ -1,28 +1,34 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Github, Twitter } from "lucide-react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { changelogData } from "@/lib/changelog-data";
|
import { changelogData } from "@/lib/changelog-data";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
import { LanguageSwitcher } from "./language-switcher";
|
|
||||||
|
|
||||||
export function Footer() {
|
export function Footer() {
|
||||||
const t = useTranslations("Footer");
|
|
||||||
const latestVersion = changelogData[0]?.version;
|
const latestVersion = changelogData[0]?.version;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className="w-full border-t bg-background">
|
<footer className="w-full border-t bg-background">
|
||||||
<div className="container relative mx-auto flex h-16 items-center justify-between px-4 md:px-6">
|
<div className="container relative mx-auto flex h-16 items-center justify-between px-4 md:px-6">
|
||||||
<div className="text-sm text-muted-foreground">
|
<div className="text-sm text-muted-foreground">
|
||||||
<p>{t("copyright", { year: new Date().getFullYear() })}</p>
|
<p>© {new Date().getFullYear()} Pascal Linxweiler</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center gap-1">
|
<div className="absolute left-1/2 top-1/2 flex -translate-x-1/2 -translate-y-1/2 items-center gap-1">
|
||||||
<LanguageSwitcher />
|
<Button variant="ghost" size="icon" asChild>
|
||||||
|
<Link href="https://github.com/" target="_blank" rel="noopener noreferrer" aria-label="GitHub">
|
||||||
|
<Github className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
|
<Button variant="ghost" size="icon" asChild>
|
||||||
|
<Link href="https://x.com/" target="_blank" rel="noopener noreferrer" aria-label="Twitter">
|
||||||
|
<Twitter className="h-4 w-4" />
|
||||||
|
</Link>
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||||
<Link href="/imprint" className="hover:text-primary transition-colors">{t("imprint")}</Link>
|
<Link href="/imprint" className="hover:text-primary transition-colors">Imprint</Link>
|
||||||
<Link href="/privacy" className="hover:text-primary transition-colors">{t("privacy")}</Link>
|
<Link href="/privacy" className="hover:text-primary transition-colors">Privacy</Link>
|
||||||
{latestVersion && (
|
{latestVersion && (
|
||||||
<Link
|
<Link
|
||||||
href="/changelog"
|
href="/changelog"
|
||||||
|
|||||||
@@ -36,7 +36,14 @@ import {
|
|||||||
TooltipProvider,
|
TooltipProvider,
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from "@/components/ui/tooltip";
|
} from "@/components/ui/tooltip";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
const aspectRatios = [
|
||||||
|
{ 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" },
|
||||||
|
];
|
||||||
|
|
||||||
const initialSettings = {
|
const initialSettings = {
|
||||||
width: "",
|
width: "",
|
||||||
@@ -57,16 +64,6 @@ const initialSettings = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function ImageConverter() {
|
export function ImageConverter() {
|
||||||
const t = useTranslations("ImageConverter");
|
|
||||||
|
|
||||||
const aspectRatios = [
|
|
||||||
{ name: t("aspectRatios.custom"), value: "custom" },
|
|
||||||
{ name: t("aspectRatios.square"), value: "1/1" },
|
|
||||||
{ name: t("aspectRatios.standard"), value: "4/3" },
|
|
||||||
{ name: t("aspectRatios.photography"), value: "3/2" },
|
|
||||||
{ name: t("aspectRatios.widescreen"), value: "16/9" },
|
|
||||||
];
|
|
||||||
|
|
||||||
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[]>([]);
|
||||||
@@ -107,7 +104,7 @@ export function ImageConverter() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (imageFiles.length === 0) {
|
if (imageFiles.length === 0) {
|
||||||
toast.error(t("toasts.noImages"));
|
toast.error("No valid image files found.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +126,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>) => {
|
||||||
@@ -171,7 +168,7 @@ export function ImageConverter() {
|
|||||||
setFilenames([]);
|
setFilenames([]);
|
||||||
setWidth(initialSettings.width);
|
setWidth(initialSettings.width);
|
||||||
setHeight(initialSettings.height);
|
setHeight(initialSettings.height);
|
||||||
toast.info(t("toasts.allCleared"));
|
toast.info("All images cleared.");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFilenameChange = (index: number, newName: string) => {
|
const handleFilenameChange = (index: number, newName: string) => {
|
||||||
@@ -300,12 +297,12 @@ export function ImageConverter() {
|
|||||||
|
|
||||||
const handleConvertAndDownloadAll = async () => {
|
const handleConvertAndDownloadAll = async () => {
|
||||||
if (images.length === 0) {
|
if (images.length === 0) {
|
||||||
toast.error(t("toasts.uploadError"));
|
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)
|
||||||
@@ -313,12 +310,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);
|
||||||
@@ -327,16 +324,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);
|
||||||
@@ -344,7 +341,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 handleResetSettings = () => {
|
const handleResetSettings = () => {
|
||||||
@@ -363,7 +360,7 @@ export function ImageConverter() {
|
|||||||
setDefaultBaseName(initialSettings.defaultBaseName);
|
setDefaultBaseName(initialSettings.defaultBaseName);
|
||||||
setScaleMode(initialSettings.scaleMode);
|
setScaleMode(initialSettings.scaleMode);
|
||||||
setObjectPosition(initialSettings.objectPosition);
|
setObjectPosition(initialSettings.objectPosition);
|
||||||
toast.success(t("toasts.settingsReset"));
|
toast.success("All settings have been reset to their defaults.");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAspectRatioChange = (value: string) => {
|
const handleAspectRatioChange = (value: string) => {
|
||||||
@@ -411,16 +408,16 @@ export function ImageConverter() {
|
|||||||
|
|
||||||
const handleApplyDefaultBaseNameToAll = () => {
|
const handleApplyDefaultBaseNameToAll = () => {
|
||||||
if (!defaultBaseName) {
|
if (!defaultBaseName) {
|
||||||
toast.error(t("toasts.defaultBaseNameMissing"));
|
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", { name: defaultBaseName, count: images.length }));
|
toast.success(`Set base name to "${defaultBaseName}" for all ${images.length} images.`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -430,7 +427,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",
|
||||||
@@ -443,8 +440,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("uploadPrompt")}</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>
|
||||||
@@ -456,25 +453,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>
|
||||||
@@ -488,7 +485,7 @@ export function ImageConverter() {
|
|||||||
<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]}
|
||||||
@@ -496,7 +493,7 @@ export function ImageConverter() {
|
|||||||
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={`${finalFilename}.${format}`}>
|
<p className="text-xs text-muted-foreground truncate mt-1" title={`${finalFilename}.${format}`}>
|
||||||
{t("finalNameLabel", { filename: `${finalFilename}.${format}` })}
|
Final name: {finalFilename}.{format}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center shrink-0">
|
<div className="flex items-center shrink-0">
|
||||||
@@ -513,7 +510,7 @@ export function ImageConverter() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("downloadSingleTooltip")}</p>
|
<p>Download this image</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -529,7 +526,7 @@ export function ImageConverter() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("removeSingleTooltip")}</p>
|
<p>Remove this image</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -547,9 +544,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("imageSettingsTitle")}</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("imageSettingsSubtitle")}
|
Adjust resolution and scaling for all images.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</AccordionTrigger>
|
</AccordionTrigger>
|
||||||
@@ -557,19 +554,19 @@ 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("aspectRatioLabel")}</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("aspectRatioTooltip")}</p>
|
<p>Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Select value={aspectRatio} onValueChange={handleAspectRatioChange}>
|
<Select value={aspectRatio} onValueChange={handleAspectRatioChange}>
|
||||||
<SelectTrigger id="aspect-ratio" className="mt-2">
|
<SelectTrigger id="aspect-ratio" className="mt-2">
|
||||||
<SelectValue placeholder={t("selectAspectRatio")} />
|
<SelectValue placeholder="Select aspect ratio" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
{aspectRatios.map((ratio) => (
|
{aspectRatios.map((ratio) => (
|
||||||
@@ -583,17 +580,17 @@ 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("widthLabel")}</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("widthTooltip")}</p>
|
<p>Set the output width in pixels. Leave blank to use the original width.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input id="width" type="number" placeholder={t("widthPlaceholder")} value={width} onChange={handleWidthChange} />
|
<Input id="width" type="number" placeholder="Auto" value={width} onChange={handleWidthChange} />
|
||||||
</div>
|
</div>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
@@ -602,34 +599,34 @@ export function ImageConverter() {
|
|||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("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("heightLabel")}</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("heightTooltip")}</p>
|
<p>Set the output height in pixels. Leave blank to use the original height.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input id="height" type="number" placeholder={t("heightPlaceholder")} value={height} onChange={handleHeightChange} />
|
<Input id="height" type="number" placeholder="Auto" value={height} onChange={handleHeightChange} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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("keepOrientationLabel")}
|
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("keepOrientationTooltip")}</p>
|
<p>Automatically swaps width and height to match the original image's orientation.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
@@ -637,35 +634,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("scalingLabel")}</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("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={t("selectScaling")} /></SelectTrigger>
|
<SelectTrigger id="scale-mode"><SelectValue placeholder="Select scaling mode" /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="fill">{t("scalingOptions.fill")}</SelectItem>
|
<SelectItem value="fill">Fill (stretch to fit)</SelectItem>
|
||||||
<SelectItem value="cover">{t("scalingOptions.cover")}</SelectItem>
|
<SelectItem value="cover">Cover (crop to fit)</SelectItem>
|
||||||
<SelectItem value="contain">{t("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("positionLabel")}</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("positionTooltip")}</p>
|
<p>Sets the anchor point for 'Cover' or 'Contain' scaling.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -678,8 +675,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("filenameSettingsTitle")}</h3>
|
<h3 className="text-lg font-medium leading-none">Filename Settings</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">{t("filenameSettingsSubtitle")}</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">
|
||||||
@@ -687,35 +684,35 @@ 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("useDefaultBaseNameLabel")}
|
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("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("defaultBaseNameLabel")}</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"
|
||||||
placeholder={t("defaultBaseNamePlaceholder")}
|
placeholder="e.g., new-york-trip"
|
||||||
value={defaultBaseName}
|
value={defaultBaseName}
|
||||||
onChange={(e) => setDefaultBaseName(e.target.value)}
|
onChange={(e) => setDefaultBaseName(e.target.value)}
|
||||||
/>
|
/>
|
||||||
<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("applyToAll")}
|
Apply to all
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("applyToAllTooltip")}</p>
|
<p>Apply this base name to all currently uploaded images.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -723,42 +720,42 @@ 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("prefixLabel")}</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("prefixTooltip")}</p>
|
<p>Add text to the beginning of every filename.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input id="prefix" placeholder={t("prefixPlaceholder")} value={prefix} onChange={(e) => setPrefix(e.target.value)} />
|
<Input id="prefix" placeholder="e.g., travel-" value={prefix} onChange={(e) => setPrefix(e.target.value)} />
|
||||||
</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("suffixLabel")}</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("suffixTooltip")}</p>
|
<p>Add text to the end of every filename (before the number).</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Input id="suffix" placeholder={t("suffixPlaceholder")} value={suffix} onChange={(e) => setSuffix(e.target.value)} />
|
<Input id="suffix" placeholder="e.g., -edit" value={suffix} onChange={(e) => setSuffix(e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
<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("useCounterLabel")}
|
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("useCounterTooltip")}</p>
|
<p>Append a numbered sequence to each filename.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Label>
|
</Label>
|
||||||
@@ -767,13 +764,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("counterStartLabel")}</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("counterStartTooltip")}</p>
|
<p>The first number to use in the sequence.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -787,13 +784,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("counterDigitsLabel")}</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("counterDigitsTooltip")}</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>
|
||||||
@@ -814,26 +811,26 @@ 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("qualitySettingsTitle")}</h3>
|
<h3 className="text-lg font-medium leading-none">Quality Settings</h3>
|
||||||
<p className="text-sm text-muted-foreground mt-1">{t("qualitySettingsSubtitle")}</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("formatLabel")}</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("formatTooltip")}</p>
|
<p>Choose the output file format for the images.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
<Select value={format} onValueChange={(value: "png" | "jpeg" | "webp") => setFormat(value)}>
|
<Select value={format} onValueChange={(value: "png" | "jpeg" | "webp") => setFormat(value)}>
|
||||||
<SelectTrigger id="format"><SelectValue placeholder={t("selectFormat")} /></SelectTrigger>
|
<SelectTrigger id="format"><SelectValue placeholder="Select format" /></SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="png">PNG</SelectItem>
|
<SelectItem value="png">PNG</SelectItem>
|
||||||
<SelectItem value="jpeg">JPEG</SelectItem>
|
<SelectItem value="jpeg">JPEG</SelectItem>
|
||||||
@@ -844,13 +841,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("qualityLabel")}</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("qualityTooltip")}</p>
|
<p>Set compression quality for JPEG/WEBP. Higher is better quality but larger file size.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
@@ -866,7 +863,7 @@ export function ImageConverter() {
|
|||||||
disabled={format === 'png'}
|
disabled={format === 'png'}
|
||||||
/>
|
/>
|
||||||
{format === 'png' && (
|
{format === 'png' && (
|
||||||
<p className="text-xs text-muted-foreground pt-1">{t("qualityDisabledHint")}</p>
|
<p className="text-xs text-muted-foreground pt-1">Quality slider is disabled for PNG (lossless format).</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -882,11 +879,11 @@ export function ImageConverter() {
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
<RotateCcw className="mr-2 h-4 w-4" />
|
<RotateCcw className="mr-2 h-4 w-4" />
|
||||||
{t("resetButton")}
|
Reset
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("resetButtonTooltip")}</p>
|
<p>Reset all settings to their default values.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
@@ -896,11 +893,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("applyButton")}
|
Apply
|
||||||
</Button>
|
</Button>
|
||||||
</TooltipTrigger>
|
</TooltipTrigger>
|
||||||
<TooltipContent>
|
<TooltipContent>
|
||||||
<p>{t("applyButtonTooltip")}</p>
|
<p>Confirm and apply all the settings above. This does not download the images.</p>
|
||||||
</TooltipContent>
|
</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { usePathname, useRouter } from "next-intl/client";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { useLocale, useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
export function LanguageSwitcher() {
|
|
||||||
const t = useTranslations("LanguageSwitcher");
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
const locale = useLocale();
|
|
||||||
|
|
||||||
const onSelectChange = (value: string) => {
|
|
||||||
router.replace(pathname, { locale: value });
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select onValueChange={onSelectChange} defaultValue={locale}>
|
|
||||||
<SelectTrigger className="w-auto">
|
|
||||||
<SelectValue />
|
|
||||||
</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