Reverted all changes back to version d33dceb08c
This commit is contained in:
100
messages/en.json
100
messages/en.json
@@ -1,100 +0,0 @@
|
||||
{
|
||||
"HomePage": {
|
||||
"title": "Image Web Exporter",
|
||||
"subtitle": "Upload a picture, then export it in a different resolution and format."
|
||||
},
|
||||
"ImageUploadArea": {
|
||||
"title": "Upload Images",
|
||||
"prompt": "Click or drag and drop to upload",
|
||||
"supportedFormats": "PNG, JPG, WEBP supported"
|
||||
},
|
||||
"ImageList": {
|
||||
"title": "Uploaded Images",
|
||||
"clearAll": "Clear All",
|
||||
"clearAllTooltip": "Remove all uploaded images.",
|
||||
"downloadAll": "Download All ({count})",
|
||||
"downloadAllDefault": "Download All",
|
||||
"converting": "Converting...",
|
||||
"downloadAllTooltip": "Convert and download all images with the current settings."
|
||||
},
|
||||
"ImageListItem": {
|
||||
"baseName": "Base Name",
|
||||
"finalName": "Final name: {finalFilename}.{format}",
|
||||
"downloadTooltip": "Download this image",
|
||||
"removeTooltip": "Remove this image"
|
||||
},
|
||||
"SettingsPanel": {
|
||||
"imageSettingsTitle": "Image Settings",
|
||||
"imageSettingsSubtitle": "Adjust resolution and scaling for all images.",
|
||||
"filenameSettingsTitle": "Filename Settings",
|
||||
"filenameSettingsSubtitle": "Customize the output filenames.",
|
||||
"qualitySettingsTitle": "Quality Settings",
|
||||
"qualitySettingsSubtitle": "Choose format and compression level."
|
||||
},
|
||||
"ImageSettings": {
|
||||
"aspectRatio": "Aspect Ratio",
|
||||
"aspectRatioTooltip": "Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.",
|
||||
"custom": "Custom",
|
||||
"square": "1:1 (Square)",
|
||||
"standard": "4:3 (Standard)",
|
||||
"photography": "3:2 (Photography)",
|
||||
"widescreen": "16:9 (Widescreen)",
|
||||
"width": "Width (px)",
|
||||
"widthTooltip": "Set the output width in pixels. Leave blank to use the original width.",
|
||||
"swapTooltip": "Swap the entered width and height values.",
|
||||
"height": "Height (px)",
|
||||
"heightTooltip": "Set the output height in pixels. Leave blank to use the original height.",
|
||||
"keepOrientation": "Keep original orientation",
|
||||
"keepOrientationTooltip": "Automatically swaps width and height to match the original image's orientation.",
|
||||
"scaling": "Scaling",
|
||||
"scalingTooltip": "Determines how the image fits into the new dimensions.",
|
||||
"fill": "Fill (stretch to fit)",
|
||||
"cover": "Cover (crop to fit)",
|
||||
"contain": "Contain (letterbox)",
|
||||
"position": "Position",
|
||||
"positionTooltip": "Sets the anchor point for 'Cover' or 'Contain' scaling."
|
||||
},
|
||||
"FilenameSettings": {
|
||||
"useDefaultBaseName": "Use default base name",
|
||||
"useDefaultBaseNameTooltip": "When enabled, all newly uploaded images will use the specified default base name.",
|
||||
"defaultBaseName": "Default base name",
|
||||
"applyToAll": "Apply to all",
|
||||
"applyToAllTooltip": "Apply this base name to all currently uploaded images.",
|
||||
"prefix": "Prefix",
|
||||
"prefixTooltip": "Add text to the beginning of every filename.",
|
||||
"suffix": "Suffix",
|
||||
"suffixTooltip": "Add text to the end of every filename (before the number).",
|
||||
"addSequentialNumber": "Add sequential number",
|
||||
"addSequentialNumberTooltip": "Append a numbered sequence to each filename.",
|
||||
"startNumber": "Start number",
|
||||
"startNumberTooltip": "The first number to use in the sequence.",
|
||||
"paddingDigits": "Padding digits",
|
||||
"paddingDigitsTooltip": "Total number of digits for the counter, padded with leading zeros (e.g., 3 for 001)."
|
||||
},
|
||||
"QualitySettings": {
|
||||
"format": "Format",
|
||||
"formatTooltip": "Choose the output file format for the images.",
|
||||
"quality": "Quality",
|
||||
"qualityTooltip": "Set compression quality for JPEG/WEBP. Higher is better quality but larger file size.",
|
||||
"pngWarning": "Quality slider is disabled for PNG (lossless format)."
|
||||
},
|
||||
"ActionButtons": {
|
||||
"reset": "Reset",
|
||||
"resetTooltip": "Reset all settings to their default values.",
|
||||
"apply": "Apply",
|
||||
"applyTooltip": "Confirm and apply all the settings above. This does not download the images."
|
||||
},
|
||||
"Footer": {
|
||||
"imprint": "Imprint",
|
||||
"privacy": "Privacy"
|
||||
},
|
||||
"ChangelogPage": {
|
||||
"back": "Back to Converter"
|
||||
},
|
||||
"ImprintPage": {
|
||||
"back": "Back to Converter"
|
||||
},
|
||||
"PrivacyPage": {
|
||||
"back": "Back to Converter"
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,6 @@
|
||||
"input-otp": "^1.4.2",
|
||||
"lucide-react": "^0.511.0",
|
||||
"next": "15.3.8",
|
||||
"next-intl": "^4.7.0",
|
||||
"next-themes": "^0.4.6",
|
||||
"react": "^19.2.1",
|
||||
"react-day-picker": "^8.10.1",
|
||||
|
||||
403
pnpm-lock.yaml
generated
403
pnpm-lock.yaml
generated
@@ -113,9 +113,6 @@ importers:
|
||||
next:
|
||||
specifier: 15.3.8
|
||||
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:
|
||||
specifier: ^0.4.6
|
||||
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':
|
||||
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':
|
||||
resolution: {integrity: sha512-u/+Jp83luQNx9AdyW2fIPGY6Y7NG68eN2ZW8FOJYL+M0i4s49+refdJdOp/A9n9HFQtQs3HIDHQvX3ZET2o7YA==}
|
||||
peerDependencies:
|
||||
@@ -452,88 +431,6 @@ packages:
|
||||
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
|
||||
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':
|
||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||
engines: {node: '>=14'}
|
||||
@@ -1150,90 +1047,15 @@ packages:
|
||||
'@radix-ui/rect@1.1.1':
|
||||
resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==}
|
||||
|
||||
'@schummar/icu-type-parser@1.21.5':
|
||||
resolution: {integrity: sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==}
|
||||
|
||||
'@standard-schema/utils@0.3.0':
|
||||
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':
|
||||
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||
|
||||
'@swc/helpers@0.5.15':
|
||||
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':
|
||||
resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==}
|
||||
|
||||
@@ -1526,9 +1348,6 @@ packages:
|
||||
decimal.js-light@2.5.1:
|
||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||
|
||||
decimal.js@10.6.0:
|
||||
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
||||
|
||||
detect-libc@2.0.4:
|
||||
resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -1680,9 +1499,6 @@ packages:
|
||||
resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
intl-messageformat@10.7.18:
|
||||
resolution: {integrity: sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==}
|
||||
|
||||
is-arrayish@0.3.2:
|
||||
resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==}
|
||||
|
||||
@@ -1797,26 +1613,9 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
negotiator@1.0.0:
|
||||
resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==}
|
||||
engines: {node: '>= 0.6'}
|
||||
|
||||
neo-async@2.6.2:
|
||||
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:
|
||||
resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==}
|
||||
peerDependencies:
|
||||
@@ -1844,9 +1643,6 @@ packages:
|
||||
sass:
|
||||
optional: true
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
node-releases@2.0.19:
|
||||
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
|
||||
|
||||
@@ -1883,10 +1679,6 @@ packages:
|
||||
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pify@2.3.0:
|
||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
@@ -1895,9 +1687,6 @@ packages:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
po-parser@2.1.1:
|
||||
resolution: {integrity: sha512-ECF4zHLbUItpUgE3OTtLKlPjeBN+fKEczj2zYjDfCGOzicNs0GK3Vg2IoAYwx7LH/XYw43fZQP6xnZ4TkNxSLQ==}
|
||||
|
||||
postcss-import@15.1.0:
|
||||
resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -2242,11 +2031,6 @@ packages:
|
||||
'@types/react':
|
||||
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:
|
||||
resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==}
|
||||
engines: {node: '>=10'}
|
||||
@@ -2361,36 +2145,6 @@ snapshots:
|
||||
|
||||
'@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))':
|
||||
dependencies:
|
||||
'@standard-schema/utils': 0.3.0
|
||||
@@ -2546,66 +2300,6 @@ snapshots:
|
||||
'@nodelib/fs.scandir': 2.1.5
|
||||
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':
|
||||
optional: true
|
||||
|
||||
@@ -3262,66 +2956,14 @@ snapshots:
|
||||
|
||||
'@radix-ui/rect@1.1.1': {}
|
||||
|
||||
'@schummar/icu-type-parser@1.21.5': {}
|
||||
|
||||
'@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/helpers@0.5.15':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@swc/types@0.1.25':
|
||||
dependencies:
|
||||
'@swc/counter': 0.1.3
|
||||
|
||||
'@types/d3-array@3.2.1': {}
|
||||
|
||||
'@types/d3-color@3.1.3': {}
|
||||
@@ -3630,9 +3272,8 @@ snapshots:
|
||||
|
||||
decimal.js-light@2.5.1: {}
|
||||
|
||||
decimal.js@10.6.0: {}
|
||||
|
||||
detect-libc@2.0.4: {}
|
||||
detect-libc@2.0.4:
|
||||
optional: true
|
||||
|
||||
detect-node-es@1.1.0: {}
|
||||
|
||||
@@ -3761,13 +3402,6 @@ snapshots:
|
||||
|
||||
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:
|
||||
optional: true
|
||||
|
||||
@@ -3862,28 +3496,8 @@ snapshots:
|
||||
|
||||
nanoid@3.3.11: {}
|
||||
|
||||
negotiator@1.0.0: {}
|
||||
|
||||
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):
|
||||
dependencies:
|
||||
react: 19.2.1
|
||||
@@ -3914,8 +3528,6 @@ snapshots:
|
||||
- '@babel/core'
|
||||
- babel-plugin-macros
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
node-releases@2.0.19: {}
|
||||
|
||||
normalize-path@3.0.0: {}
|
||||
@@ -3939,14 +3551,10 @@ snapshots:
|
||||
|
||||
picomatch@2.3.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pify@2.3.0: {}
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
po-parser@2.1.1: {}
|
||||
|
||||
postcss-import@15.1.0(postcss@8.5.3):
|
||||
dependencies:
|
||||
postcss: 8.5.3
|
||||
@@ -4313,13 +3921,6 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@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):
|
||||
dependencies:
|
||||
detect-node-es: 1.1.0
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
import { Changelog } from "@/components/changelog";
|
||||
import Link from "next-intl/link";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ArrowLeft } from "lucide-react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function ChangelogPage() {
|
||||
const t = useTranslations("ChangelogPage");
|
||||
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="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('back')}
|
||||
Back to Converter
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
@@ -1,18 +1,16 @@
|
||||
import Link from "next-intl/link";
|
||||
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 { useTranslations } from "next-intl";
|
||||
|
||||
export default function ImprintPage() {
|
||||
const t = useTranslations("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('back')}
|
||||
Back to Converter
|
||||
</Link>
|
||||
</Button>
|
||||
<main className="w-full">
|
||||
@@ -1,11 +1,9 @@
|
||||
import type { Metadata } from "next";
|
||||
import { Geist, Geist_Mono } from "next/font/google";
|
||||
import "../globals.css";
|
||||
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",
|
||||
@@ -22,21 +20,16 @@ export const metadata: Metadata = {
|
||||
description: "Upload a picture, then export it in a different resolution and format.",
|
||||
};
|
||||
|
||||
export default async function RootLayout({
|
||||
export default function RootLayout({
|
||||
children,
|
||||
params: { locale }
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
params: { locale: string };
|
||||
}>) {
|
||||
const messages = await getMessages();
|
||||
|
||||
return (
|
||||
<html lang={locale} suppressHydrationWarning>
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body
|
||||
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
|
||||
>
|
||||
<NextIntlClientProvider messages={messages}>
|
||||
<ThemeProvider
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
@@ -47,7 +40,6 @@ export default async function RootLayout({
|
||||
<Footer />
|
||||
<Toaster />
|
||||
</ThemeProvider>
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
@@ -1,17 +1,15 @@
|
||||
import { ImageConverter } from "@/components/image-converter";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export default function Home() {
|
||||
const t = useTranslations('HomePage');
|
||||
return (
|
||||
<div className="relative flex flex-col items-center justify-center min-h-screen p-4 sm:p-8 bg-gray-50 dark:bg-background font-[family-name:var(--font-geist-sans)]">
|
||||
<main className="flex flex-col items-center w-full max-w-6xl z-10">
|
||||
<div className="text-center mb-8">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-gray-900 dark:text-gray-100 sm:text-5xl">
|
||||
{t('title')}
|
||||
Image Web Exporter
|
||||
</h1>
|
||||
<p className="mt-3 text-lg text-gray-600 dark:text-gray-400">
|
||||
{t('subtitle')}
|
||||
Upload a picture, then export it in a different resolution and format.
|
||||
</p>
|
||||
</div>
|
||||
<ImageConverter />
|
||||
@@ -1,18 +1,16 @@
|
||||
import Link from "next-intl/link";
|
||||
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 { useTranslations } from "next-intl";
|
||||
|
||||
export default function PrivacyPage() {
|
||||
const t = useTranslations("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('back')}
|
||||
Back to Converter
|
||||
</Link>
|
||||
</Button>
|
||||
<main className="w-full">
|
||||
@@ -4,14 +4,12 @@ import { Button } from "@/components/ui/button";
|
||||
import { Check, RotateCcw } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { toast } from "sonner";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface ActionButtonsProps {
|
||||
onReset: () => void;
|
||||
}
|
||||
|
||||
export function ActionButtons({ onReset }: ActionButtonsProps) {
|
||||
const t = useTranslations("ActionButtons");
|
||||
const handleApply = () => {
|
||||
toast.info("Settings updated and will be used for all downloads.");
|
||||
};
|
||||
@@ -22,18 +20,18 @@ export function ActionButtons({ onReset }: ActionButtonsProps) {
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button onClick={onReset} className="w-full" variant="outline">
|
||||
<RotateCcw className="mr-2 h-4 w-4" /> {t('reset')}
|
||||
<RotateCcw className="mr-2 h-4 w-4" /> Reset
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('resetTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Reset all settings to their default values.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button onClick={handleApply} className="w-full">
|
||||
<Check className="mr-2 h-4 w-4" /> {t('apply')}
|
||||
<Check className="mr-2 h-4 w-4" /> Apply
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('applyTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Confirm and apply all the settings above. This does not download the images.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next-intl/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 { useTranslations } from "next-intl";
|
||||
|
||||
export function Footer() {
|
||||
const t = useTranslations("Footer");
|
||||
const latestVersion = changelogData[0]?.version;
|
||||
|
||||
return (
|
||||
@@ -31,8 +27,8 @@ export function Footer() {
|
||||
</div>
|
||||
|
||||
<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="/privacy" className="hover:text-primary transition-colors">{t('privacy')}</Link>
|
||||
<Link href="/imprint" className="hover:text-primary transition-colors">Imprint</Link>
|
||||
<Link href="/privacy" className="hover:text-primary transition-colors">Privacy</Link>
|
||||
{latestVersion && (
|
||||
<Link
|
||||
href="/changelog"
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { Download, X } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { generateFinalFilename } from "@/lib/image-processor";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface ImageListItemProps {
|
||||
image: ImageFile;
|
||||
@@ -28,24 +27,22 @@ export function ImageListItem({
|
||||
onDownload,
|
||||
isProcessing,
|
||||
}: ImageListItemProps) {
|
||||
const t = useTranslations("ImageListItem");
|
||||
const finalFilename = generateFinalFilename(image.filename, settings, index);
|
||||
const finalNameText = t('finalName', { finalFilename, format: settings.format });
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<div className="p-4 border rounded-lg flex items-center gap-4">
|
||||
<img src={image.previewUrl} alt={`Preview ${index + 1}`} className="w-20 h-20 object-cover rounded-md shrink-0" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<Label htmlFor={`filename-${index}`} className="text-xs text-muted-foreground">{t('baseName')}</Label>
|
||||
<Label htmlFor={`filename-${index}`} className="text-xs text-muted-foreground">Base Name</Label>
|
||||
<Input
|
||||
id={`filename-${index}`}
|
||||
value={image.filename}
|
||||
onChange={(e) => onFilenameChange(index, e.target.value)}
|
||||
className="text-sm font-medium h-8 mt-1"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground truncate mt-1" title={finalNameText}>
|
||||
{finalNameText}
|
||||
<p className="text-xs text-muted-foreground truncate mt-1" title={`${finalFilename}.${settings.format}`}>
|
||||
Final name: {finalFilename}.{settings.format}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center shrink-0">
|
||||
@@ -61,7 +58,7 @@ export function ImageListItem({
|
||||
<Download className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('downloadTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Download this image</p></TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
@@ -75,7 +72,7 @@ export function ImageListItem({
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('removeTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Remove this image</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { Download, Trash2 } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { ImageListItem } from "./image-list-item";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface ImageListProps {
|
||||
images: ImageFile[];
|
||||
@@ -31,8 +30,6 @@ export function ImageList({
|
||||
isConverting,
|
||||
convertingIndex,
|
||||
}: ImageListProps) {
|
||||
const t = useTranslations("ImageList");
|
||||
|
||||
if (images.length === 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -44,24 +41,24 @@ export function ImageList({
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex justify-between items-center">
|
||||
<CardTitle>{t('title')}</CardTitle>
|
||||
<CardTitle>Uploaded Images</CardTitle>
|
||||
<div className="flex items-center gap-2">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="ghost" size="sm" onClick={onClearAll} disabled={isProcessing}>
|
||||
<Trash2 className="mr-2 h-4 w-4" />{t('clearAll')}
|
||||
<Trash2 className="mr-2 h-4 w-4" />Clear All
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('clearAllTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Remove all uploaded images.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button onClick={onDownloadAll} disabled={isProcessing}>
|
||||
<Download className="mr-2 h-4 w-4" />
|
||||
{isConverting ? t('converting') : t('downloadAll', { count: images.length })}
|
||||
{isConverting ? "Converting..." : `Download All (${images.length})`}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('downloadAllTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Convert and download all images with the current settings.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,14 +5,12 @@ import { Input } from "@/components/ui/input";
|
||||
import { Upload } from "lucide-react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { Card, CardContent } from "./ui/card";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface ImageUploadAreaProps {
|
||||
onFilesSelected: (files: FileList | null) => void;
|
||||
}
|
||||
|
||||
export function ImageUploadArea({ onFilesSelected }: ImageUploadAreaProps) {
|
||||
const t = useTranslations("ImageUploadArea");
|
||||
const [isDraggingOver, setIsDraggingOver] = useState(false);
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@@ -41,7 +39,7 @@ export function ImageUploadArea({ onFilesSelected }: ImageUploadAreaProps) {
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="space-y-4">
|
||||
<h3 className="text-lg font-medium">{t('title')}</h3>
|
||||
<h3 className="text-lg font-medium">Upload Images</h3>
|
||||
<div
|
||||
className={cn(
|
||||
"w-full h-48 rounded-lg border-2 border-dashed flex items-center justify-center relative transition-colors cursor-pointer hover:border-primary/60",
|
||||
@@ -54,8 +52,8 @@ export function ImageUploadArea({ onFilesSelected }: ImageUploadAreaProps) {
|
||||
>
|
||||
<div className="flex flex-col items-center justify-center text-center text-muted-foreground">
|
||||
<Upload className="w-8 h-8 mb-2" />
|
||||
<p className="font-semibold">{t('prompt')}</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">{t('supportedFormats')}</p>
|
||||
<p className="font-semibold">Click or drag and drop to upload</p>
|
||||
<p className="text-xs text-muted-foreground mt-1">PNG, JPG, WEBP supported</p>
|
||||
</div>
|
||||
<Input type="file" ref={fileInputRef} onChange={handleImageChange} className="hidden" accept="image/*" multiple />
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,6 @@ import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/
|
||||
import { ImageSettings } from "./settings/image-settings";
|
||||
import { FilenameSettings } from "./settings/filename-settings";
|
||||
import { QualitySettings } from "./settings/quality-settings";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface SettingsPanelProps {
|
||||
settings: ConversionSettings;
|
||||
@@ -24,14 +23,13 @@ export function SettingsPanel({
|
||||
onApplyDefaultBaseNameToAll,
|
||||
hasImages,
|
||||
}: SettingsPanelProps) {
|
||||
const t = useTranslations("SettingsPanel");
|
||||
return (
|
||||
<Accordion type="single" collapsible defaultValue="image-settings" className="w-full space-y-4">
|
||||
<AccordionItem value="image-settings" className="border rounded-lg bg-card">
|
||||
<AccordionTrigger className="p-6 hover:no-underline">
|
||||
<div className="text-left">
|
||||
<h3 className="text-lg font-medium leading-none">{t('imageSettingsTitle')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">{t('imageSettingsSubtitle')}</p>
|
||||
<h3 className="text-lg font-medium leading-none">Image Settings</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">Adjust resolution and scaling for all images.</p>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="px-6 pb-6">
|
||||
@@ -47,8 +45,8 @@ export function SettingsPanel({
|
||||
<AccordionItem value="filename-settings" className="border rounded-lg bg-card">
|
||||
<AccordionTrigger className="p-6 hover:no-underline">
|
||||
<div className="text-left">
|
||||
<h3 className="text-lg font-medium leading-none">{t('filenameSettingsTitle')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">{t('filenameSettingsSubtitle')}</p>
|
||||
<h3 className="text-lg font-medium leading-none">Filename Settings</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">Customize the output filenames.</p>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="px-6 pb-6">
|
||||
@@ -64,8 +62,8 @@ export function SettingsPanel({
|
||||
<AccordionItem value="quality-settings" className="border rounded-lg bg-card">
|
||||
<AccordionTrigger className="p-6 hover:no-underline">
|
||||
<div className="text-left">
|
||||
<h3 className="text-lg font-medium leading-none">{t('qualitySettingsTitle')}</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">{t('qualitySettingsSubtitle')}</p>
|
||||
<h3 className="text-lg font-medium leading-none">Quality Settings</h3>
|
||||
<p className="text-sm text-muted-foreground mt-1">Choose format and compression level.</p>
|
||||
</div>
|
||||
</AccordionTrigger>
|
||||
<AccordionContent className="px-6 pb-6">
|
||||
|
||||
@@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface FilenameSettingsProps {
|
||||
settings: ConversionSettings;
|
||||
@@ -22,22 +21,21 @@ export function FilenameSettings({
|
||||
onApplyDefaultBaseNameToAll,
|
||||
hasImages,
|
||||
}: FilenameSettingsProps) {
|
||||
const t = useTranslations("FilenameSettings");
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Switch id="use-default-base-name" checked={settings.useDefaultBaseName} onCheckedChange={(checked) => onSettingsChange({ useDefaultBaseName: checked })} />
|
||||
<Label htmlFor="use-default-base-name" className="flex items-center gap-1.5 cursor-pointer">
|
||||
{t('useDefaultBaseName')}
|
||||
Use default base name
|
||||
<Tooltip>
|
||||
<TooltipTrigger onClick={(e) => e.preventDefault()}><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('useDefaultBaseNameTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>When enabled, all newly uploaded images will use the specified default base name.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</Label>
|
||||
</div>
|
||||
{settings.useDefaultBaseName && (
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="default-base-name">{t('defaultBaseName')}</Label>
|
||||
<Label htmlFor="default-base-name">Default base name</Label>
|
||||
<div className="flex items-center gap-2">
|
||||
<Input
|
||||
id="default-base-name"
|
||||
@@ -48,30 +46,30 @@ export function FilenameSettings({
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button variant="outline" size="sm" onClick={onApplyDefaultBaseNameToAll} disabled={!settings.defaultBaseName || !hasImages}>
|
||||
{t('applyToAll')}
|
||||
Apply to all
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('applyToAllTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Apply this base name to all currently uploaded images.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="prefix">{t('prefix')}</Label>
|
||||
<Label htmlFor="prefix">Prefix</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('prefixTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Add text to the beginning of every filename.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input id="prefix" placeholder="e.g., travel-" value={settings.prefix} onChange={(e) => onSettingsChange({ prefix: e.target.value })} />
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="suffix">{t('suffix')}</Label>
|
||||
<Label htmlFor="suffix">Suffix</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('suffixTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Add text to the end of every filename (before the number).</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input id="suffix" placeholder="e.g., -edit" value={settings.suffix} onChange={(e) => onSettingsChange({ suffix: e.target.value })} />
|
||||
@@ -79,10 +77,10 @@ export function FilenameSettings({
|
||||
<div className="flex items-center space-x-2 pt-2">
|
||||
<Switch id="use-counter" checked={settings.useCounter} onCheckedChange={(checked) => onSettingsChange({ useCounter: checked })} />
|
||||
<Label htmlFor="use-counter" className="flex items-center gap-1.5 cursor-pointer">
|
||||
{t('addSequentialNumber')}
|
||||
Add sequential number
|
||||
<Tooltip>
|
||||
<TooltipTrigger onClick={(e) => e.preventDefault()}><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('addSequentialNumberTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Append a numbered sequence to each filename.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</Label>
|
||||
</div>
|
||||
@@ -90,10 +88,10 @@ export function FilenameSettings({
|
||||
<div className="grid grid-cols-2 gap-4 pt-2">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="counter-start">{t('startNumber')}</Label>
|
||||
<Label htmlFor="counter-start">Start number</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('startNumberTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>The first number to use in the sequence.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input
|
||||
@@ -106,10 +104,10 @@ export function FilenameSettings({
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="counter-digits">{t('paddingDigits')}</Label>
|
||||
<Label htmlFor="counter-digits">Padding digits</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('paddingDigitsTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Total number of digits for the counter, padded with leading zeros (e.g., 3 for 001).</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input
|
||||
|
||||
@@ -9,7 +9,14 @@ import { Checkbox } from "@/components/ui/checkbox";
|
||||
import { ArrowRightLeft, HelpCircle } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { ObjectPositionControl } from "@/components/object-position-control";
|
||||
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" },
|
||||
];
|
||||
|
||||
interface ImageSettingsProps {
|
||||
settings: ConversionSettings;
|
||||
@@ -24,24 +31,14 @@ export function ImageSettings({
|
||||
onAspectRatioChange,
|
||||
onSwapDimensions,
|
||||
}: ImageSettingsProps) {
|
||||
const t = useTranslations("ImageSettings");
|
||||
|
||||
const aspectRatios = [
|
||||
{ name: t('custom'), value: "custom" },
|
||||
{ name: t('square'), value: "1/1" },
|
||||
{ name: t('standard'), value: "4/3" },
|
||||
{ name: t('photography'), value: "3/2" },
|
||||
{ name: t('widescreen'), value: "16/9" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="aspect-ratio">{t('aspectRatio')}</Label>
|
||||
<Label htmlFor="aspect-ratio">Aspect Ratio</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('aspectRatioTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select value={settings.aspectRatio} onValueChange={onAspectRatioChange}>
|
||||
@@ -56,10 +53,10 @@ export function ImageSettings({
|
||||
<div className="flex items-end gap-2">
|
||||
<div className="space-y-2 flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="width">{t('width')}</Label>
|
||||
<Label htmlFor="width">Width (px)</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('widthTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Set the output width in pixels. Leave blank to use the original width.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input id="width" type="number" placeholder="Auto" value={settings.width} onChange={(e) => { onSettingsChange({ width: e.target.value, aspectRatio: 'custom' }) }} />
|
||||
@@ -70,14 +67,14 @@ export function ImageSettings({
|
||||
<ArrowRightLeft className="h-4 w-4" />
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent><p>{t('swapTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Swap the entered width and height values.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
<div className="space-y-2 flex-1">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="height">{t('height')}</Label>
|
||||
<Label htmlFor="height">Height (px)</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('heightTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Set the output height in pixels. Leave blank to use the original height.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Input id="height" type="number" placeholder="Auto" value={settings.height} onChange={(e) => { onSettingsChange({ height: e.target.value, aspectRatio: 'custom' }) }} />
|
||||
@@ -86,37 +83,37 @@ export function ImageSettings({
|
||||
<div className="flex items-center space-x-2 pt-2">
|
||||
<Checkbox id="keep-orientation" checked={settings.keepOrientation} onCheckedChange={(checked) => onSettingsChange({ keepOrientation: Boolean(checked) })} />
|
||||
<Label htmlFor="keep-orientation" className="cursor-pointer flex items-center gap-1.5">
|
||||
{t('keepOrientation')}
|
||||
Keep original orientation
|
||||
<Tooltip>
|
||||
<TooltipTrigger onClick={(e) => e.preventDefault()}><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('keepOrientationTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Automatically swaps width and height to match the original image's orientation.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</Label>
|
||||
</div>
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="scale-mode">{t('scaling')}</Label>
|
||||
<Label htmlFor="scale-mode">Scaling</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('scalingTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Determines how the image fits into the new dimensions.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select value={settings.scaleMode} onValueChange={(value) => onSettingsChange({ scaleMode: value as any })}>
|
||||
<SelectTrigger id="scale-mode"><SelectValue placeholder="Select scaling mode" /></SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="fill">{t('fill')}</SelectItem>
|
||||
<SelectItem value="cover">{t('cover')}</SelectItem>
|
||||
<SelectItem value="contain">{t('contain')}</SelectItem>
|
||||
<SelectItem value="fill">Fill (stretch to fit)</SelectItem>
|
||||
<SelectItem value="cover">Cover (crop to fit)</SelectItem>
|
||||
<SelectItem value="contain">Contain (letterbox)</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
{settings.scaleMode !== 'fill' && (
|
||||
<div className="mt-4 space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label>{t('position')}</Label>
|
||||
<Label>Position</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('positionTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Sets the anchor point for 'Cover' or 'Contain' scaling.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<ObjectPositionControl value={settings.objectPosition} onChange={(pos) => onSettingsChange({ objectPosition: pos as ObjectPosition })} />
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
||||
import { Slider } from "@/components/ui/slider";
|
||||
import { HelpCircle } from "lucide-react";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
interface QualitySettingsProps {
|
||||
settings: ConversionSettings;
|
||||
@@ -14,15 +13,14 @@ interface QualitySettingsProps {
|
||||
}
|
||||
|
||||
export function QualitySettings({ settings, onSettingsChange }: QualitySettingsProps) {
|
||||
const t = useTranslations("QualitySettings");
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="format">{t('format')}</Label>
|
||||
<Label htmlFor="format">Format</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('formatTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Choose the output file format for the images.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Select value={settings.format} onValueChange={(value: ImageFormat) => onSettingsChange({ format: value })}>
|
||||
@@ -37,10 +35,10 @@ export function QualitySettings({ settings, onSettingsChange }: QualitySettingsP
|
||||
<div className="space-y-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<Label htmlFor="quality">{t('quality')}</Label>
|
||||
<Label htmlFor="quality">Quality</Label>
|
||||
<Tooltip>
|
||||
<TooltipTrigger><HelpCircle className="h-4 w-4 text-muted-foreground" /></TooltipTrigger>
|
||||
<TooltipContent><p>{t('qualityTooltip')}</p></TooltipContent>
|
||||
<TooltipContent><p>Set compression quality for JPEG/WEBP. Higher is better quality but larger file size.</p></TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<span className="text-sm text-muted-foreground">{settings.quality}%</span>
|
||||
@@ -55,7 +53,7 @@ export function QualitySettings({ settings, onSettingsChange }: QualitySettingsP
|
||||
disabled={settings.format === 'png'}
|
||||
/>
|
||||
{settings.format === 'png' && (
|
||||
<p className="text-xs text-muted-foreground pt-1">{t('pngWarning')}</p>
|
||||
<p className="text-xs text-muted-foreground pt-1">Quality slider is disabled for PNG (lossless format).</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
14
src/i18n.ts
14
src/i18n.ts
@@ -1,14 +0,0 @@
|
||||
import {getRequestConfig} from 'next-intl/server';
|
||||
|
||||
export const locales = ['en'];
|
||||
export const defaultLocale = 'en';
|
||||
|
||||
export default getRequestConfig(async ({locale}) => {
|
||||
if (!locales.includes(locale as any)) {
|
||||
locale = defaultLocale;
|
||||
}
|
||||
|
||||
return {
|
||||
messages: (await import(`../messages/${locale}.json`)).default
|
||||
};
|
||||
});
|
||||
@@ -1,10 +0,0 @@
|
||||
import createMiddleware from 'next-intl/middleware';
|
||||
|
||||
export default createMiddleware({
|
||||
locales: ['en'],
|
||||
defaultLocale: 'en'
|
||||
});
|
||||
|
||||
export const config = {
|
||||
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
|
||||
};
|
||||
Reference in New Issue
Block a user