diff --git a/i18n.ts b/i18n.ts
new file mode 100644
index 0000000..39ea657
--- /dev/null
+++ b/i18n.ts
@@ -0,0 +1,5 @@
+import {getRequestConfig} from 'next-intl/server';
+
+export default getRequestConfig(async ({locale}) => ({
+ messages: (await import(`./messages/${locale}.json`)).default
+}));
\ No newline at end of file
diff --git a/messages/en.json b/messages/en.json
new file mode 100644
index 0000000..733903d
--- /dev/null
+++ b/messages/en.json
@@ -0,0 +1,87 @@
+{
+ "HomePage": {
+ "title": "Image Web Exporter",
+ "description": "Upload a picture, then export it in a different resolution and format."
+ },
+ "LanguageSwitcher": {
+ "placeholder": "Language"
+ },
+ "ImageConverter": {
+ "uploadTitle": "Upload Images",
+ "uploadButton": "Click or drag and drop to upload",
+ "uploadHint": "PNG, JPG, WEBP supported",
+ "uploadedImagesTitle": "Uploaded Images",
+ "clearAll": "Clear All",
+ "downloadAll": "Download All ({count})",
+ "converting": "Converting...",
+ "baseNameLabel": "Base Name",
+ "finalNameLabel": "Final name: {filename}",
+ "downloadImageTooltip": "Download this image",
+ "removeImageTooltip": "Remove this image",
+ "imageSettingsTitle": "Image Settings",
+ "imageSettingsDescription": "Adjust resolution and scaling for all images.",
+ "aspectRatioLabel": "Aspect Ratio",
+ "aspectRatioTooltip": "Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.",
+ "customAspectRatio": "Custom",
+ "squareAspectRatio": "1:1 (Square)",
+ "standardAspectRatio": "4:3 (Standard)",
+ "photoAspectRatio": "3:2 (Photography)",
+ "widescreenAspectRatio": "16:9 (Widescreen)",
+ "widthLabel": "Width (px)",
+ "widthTooltip": "Set the output width in pixels. Leave blank to use the original width.",
+ "heightLabel": "Height (px)",
+ "heightTooltip": "Set the output height in pixels. Leave blank to use the original height.",
+ "originalPlaceholder": "Original",
+ "swapDimensionsTooltip": "Swap the entered width and height values.",
+ "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.",
+ "scalingFill": "Fill (stretch to fit)",
+ "scalingCover": "Cover (crop to fit)",
+ "scalingContain": "Contain (letterbox)",
+ "positionLabel": "Position",
+ "positionTooltip": "Sets the anchor point for 'Cover' or 'Contain' scaling.",
+ "filenameSettingsTitle": "Filename Settings",
+ "filenameSettingsDescription": "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",
+ "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.",
+ "suffixLabel": "Suffix",
+ "suffixTooltip": "Add text to the end of every filename (before the number).",
+ "addSequentialNumberLabel": "Add sequential number",
+ "addSequentialNumberTooltip": "Append a numbered sequence to each filename.",
+ "startNumberLabel": "Start number",
+ "startNumberTooltip": "The first number to use in the sequence.",
+ "paddingDigitsLabel": "Padding digits",
+ "paddingDigitsTooltip": "Total number of digits for the counter, padded with leading zeros (e.g., 3 for 001).",
+ "qualitySettingsTitle": "Quality Settings",
+ "qualitySettingsDescription": "Choose format and compression level.",
+ "formatLabel": "Format",
+ "formatTooltip": "Choose the output file format for the images.",
+ "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).",
+ "applySettings": "Apply Settings",
+ "applySettingsTooltip": "Confirm and apply all the settings above. This does not download the images.",
+ "toasts": {
+ "noImages": "Please upload images first.",
+ "conversionStarting": "Starting conversion for {count} images...",
+ "conversionSuccess": "Successfully exported all {count} images!",
+ "conversionError": "An unknown error occurred during conversion.",
+ "singleConversionStarting": "Starting conversion for {filename}...",
+ "singleConversionSuccess": "Successfully exported {filename}!",
+ "settingsApplied": "Settings updated and will be used for all downloads.",
+ "noValidImages": "No valid image files found.",
+ "imagesAdded": "{count} image(s) added.",
+ "allCleared": "All images cleared.",
+ "noDefaultBaseName": "Please enter a default base name to apply.",
+ "uploadImagesFirst": "Upload some images first.",
+ "baseNameApplied": "Set base name to \\\"{baseName}\\\" for all {count} images."
+ }
+ }
+}
\ No newline at end of file
diff --git a/messages/es.json b/messages/es.json
new file mode 100644
index 0000000..c156d03
--- /dev/null
+++ b/messages/es.json
@@ -0,0 +1,87 @@
+{
+ "HomePage": {
+ "title": "Exportador de Imágenes Web",
+ "description": "Sube una imagen, luego expórtala en una resolución y formato diferente."
+ },
+ "LanguageSwitcher": {
+ "placeholder": "Idioma"
+ },
+ "ImageConverter": {
+ "uploadTitle": "Subir Imágenes",
+ "uploadButton": "Haz clic o arrastra y suelta para subir",
+ "uploadHint": "Soporta PNG, JPG, WEBP",
+ "uploadedImagesTitle": "Imágenes Subidas",
+ "clearAll": "Limpiar Todo",
+ "downloadAll": "Descargar Todo ({count})",
+ "converting": "Convirtiendo...",
+ "baseNameLabel": "Nombre Base",
+ "finalNameLabel": "Nombre final: {filename}",
+ "downloadImageTooltip": "Descargar esta imagen",
+ "removeImageTooltip": "Eliminar esta imagen",
+ "imageSettingsTitle": "Ajustes de Imagen",
+ "imageSettingsDescription": "Ajusta la resolución y el escalado para todas las imágenes.",
+ "aspectRatioLabel": "Relación de Aspecto",
+ "aspectRatioTooltip": "Elige una relación de aspecto preestablecida o selecciona 'Personalizado' para introducir las dimensiones manualmente.",
+ "customAspectRatio": "Personalizado",
+ "squareAspectRatio": "1:1 (Cuadrado)",
+ "standardAspectRatio": "4:3 (Estándar)",
+ "photoAspectRatio": "3:2 (Fotografía)",
+ "widescreenAspectRatio": "16:9 (Panorámico)",
+ "widthLabel": "Ancho (px)",
+ "widthTooltip": "Establece el ancho de salida en píxeles. Déjalo en blanco para usar el ancho original.",
+ "heightLabel": "Alto (px)",
+ "heightTooltip": "Establece la altura de salida en píxeles. Déjala en blanco para usar la altura original.",
+ "originalPlaceholder": "Original",
+ "swapDimensionsTooltip": "Intercambia los valores de ancho y alto introducidos.",
+ "keepOrientationLabel": "Mantener orientación original",
+ "keepOrientationTooltip": "Intercambia automáticamente el ancho y el alto para que coincida con la orientación de la imagen original.",
+ "scalingLabel": "Escalado",
+ "scalingTooltip": "Determina cómo se ajusta la imagen a las nuevas dimensiones.",
+ "scalingFill": "Rellenar (estirar para ajustar)",
+ "scalingCover": "Cubrir (recortar para ajustar)",
+ "scalingContain": "Contener (letterbox)",
+ "positionLabel": "Posición",
+ "positionTooltip": "Establece el punto de anclaje para el escalado 'Cubrir' o 'Contener'.",
+ "filenameSettingsTitle": "Ajustes de Nombre de Archivo",
+ "filenameSettingsDescription": "Personaliza los nombres de los archivos de salida.",
+ "useDefaultBaseNameLabel": "Usar nombre base por defecto",
+ "useDefaultBaseNameTooltip": "Cuando está activado, todas las imágenes recién subidas usarán el nombre base por defecto especificado.",
+ "defaultBaseNameLabel": "Nombre base por defecto",
+ "applyToAll": "Aplicar a todo",
+ "applyToAllTooltip": "Aplica este nombre base a todas las imágenes subidas actualmente.",
+ "prefixLabel": "Prefijo",
+ "prefixTooltip": "Añade texto al principio de cada nombre de archivo.",
+ "suffixLabel": "Sufijo",
+ "suffixTooltip": "Añade texto al final de cada nombre de archivo (antes del número).",
+ "addSequentialNumberLabel": "Añadir número secuencial",
+ "addSequentialNumberTooltip": "Añade una secuencia numerada a cada nombre de archivo.",
+ "startNumberLabel": "Número inicial",
+ "startNumberTooltip": "El primer número a usar en la secuencia.",
+ "paddingDigitsLabel": "Dígitos de relleno",
+ "paddingDigitsTooltip": "Número total de dígitos para el contador, rellenado con ceros a la izquierda (ej. 3 para 001).",
+ "qualitySettingsTitle": "Ajustes de Calidad",
+ "qualitySettingsDescription": "Elige el formato y el nivel de compresión.",
+ "formatLabel": "Formato",
+ "formatTooltip": "Elige el formato de archivo de salida para las imágenes.",
+ "qualityLabel": "Calidad",
+ "qualityTooltip": "Establece la calidad de compresión para JPEG/WEBP. Más alto es mejor calidad pero mayor tamaño de archivo.",
+ "qualityDisabledHint": "El control de calidad está desactivado para PNG (formato sin pérdidas).",
+ "applySettings": "Aplicar Ajustes",
+ "applySettingsTooltip": "Confirma y aplica todos los ajustes anteriores. Esto no descarga las imágenes.",
+ "toasts": {
+ "noImages": "Por favor, sube imágenes primero.",
+ "conversionStarting": "Iniciando conversión para {count} imágenes...",
+ "conversionSuccess": "¡Se exportaron con éxito todas las {count} imágenes!",
+ "conversionError": "Ocurrió un error desconocido durante la conversión.",
+ "singleConversionStarting": "Iniciando conversión para {filename}...",
+ "singleConversionSuccess": "¡Se exportó con éxito {filename}!",
+ "settingsApplied": "Ajustes actualizados y se usarán para todas las descargas.",
+ "noValidImages": "No se encontraron archivos de imagen válidos.",
+ "imagesAdded": "{count} imagen(es) añadida(s).",
+ "allCleared": "Todas las imágenes eliminadas.",
+ "noDefaultBaseName": "Por favor, introduce un nombre base por defecto para aplicar.",
+ "uploadImagesFirst": "Sube algunas imágenes primero.",
+ "baseNameApplied": "Se estableció el nombre base a \\\"{baseName}\\\" para todas las {count} imágenes."
+ }
+ }
+}
\ No newline at end of file
diff --git a/middleware.ts b/middleware.ts
new file mode 100644
index 0000000..a178146
--- /dev/null
+++ b/middleware.ts
@@ -0,0 +1,10 @@
+import createMiddleware from 'next-intl/middleware';
+
+export default createMiddleware({
+ locales: ['en', 'es'],
+ defaultLocale: 'en'
+});
+
+export const config = {
+ matcher: ['/', '/(es|en)/:path*']
+};
\ No newline at end of file
diff --git a/package.json b/package.json
index e6537d8..530c500 100644
--- a/package.json
+++ b/package.json
@@ -44,6 +44,7 @@
"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",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f1383c3..b2c681b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -113,6 +113,9 @@ 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)
@@ -222,6 +225,24 @@ 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:
@@ -431,6 +452,88 @@ 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'}
@@ -1047,15 +1150,90 @@ 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==}
@@ -1348,6 +1526,9 @@ 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'}
@@ -1499,6 +1680,9 @@ 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==}
@@ -1613,9 +1797,26 @@ 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:
@@ -1643,6 +1844,9 @@ 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==}
@@ -1679,6 +1883,10 @@ 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'}
@@ -1687,6 +1895,9 @@ 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'}
@@ -2031,6 +2242,11 @@ 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'}
@@ -2145,6 +2361,36 @@ 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
@@ -2300,6 +2546,66 @@ 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
@@ -2956,14 +3262,66 @@ 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': {}
@@ -3272,8 +3630,9 @@ snapshots:
decimal.js-light@2.5.1: {}
- detect-libc@2.0.4:
- optional: true
+ decimal.js@10.6.0: {}
+
+ detect-libc@2.0.4: {}
detect-node-es@1.1.0: {}
@@ -3402,6 +3761,13 @@ 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
@@ -3496,8 +3862,28 @@ 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
@@ -3528,6 +3914,8 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
+ node-addon-api@7.1.1: {}
+
node-releases@2.0.19: {}
normalize-path@3.0.0: {}
@@ -3551,10 +3939,14 @@ 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
@@ -3921,6 +4313,13 @@ 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
diff --git a/src/app/layout.tsx b/src/app/[locale]/layout.tsx
similarity index 57%
rename from src/app/layout.tsx
rename to src/app/[locale]/layout.tsx
index 710fbc6..a816798 100644
--- a/src/app/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -1,8 +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 { NextIntlClientProvider, useMessages } from "next-intl";
const geistSans = Geist({
variable: "--font-geist-sans",
@@ -21,23 +22,29 @@ export const metadata: Metadata = {
export default function RootLayout({
children,
+ params: { locale },
}: Readonly<{
children: React.ReactNode;
+ params: { locale: string };
}>) {
+ const messages = useMessages();
+
return (
-
+
-
- {children}
-
-
+
+
+ {children}
+
+
+
);
diff --git a/src/app/page.tsx b/src/app/[locale]/page.tsx
similarity index 68%
rename from src/app/page.tsx
rename to src/app/[locale]/page.tsx
index 42403e1..7d4900b 100644
--- a/src/app/page.tsx
+++ b/src/app/[locale]/page.tsx
@@ -1,15 +1,22 @@
import { ImageConverter } from "@/components/image-converter";
+import { LanguageSwitcher } from "@/components/language-switcher";
+import { useTranslations } from "next-intl";
export default function Home() {
+ const t = useTranslations("HomePage");
+
return (
+
+
+
- Image Web Exporter
+ {t("title")}
- Upload a picture, then export it in a different resolution and format.
+ {t("description")}
diff --git a/src/components/image-converter.tsx b/src/components/image-converter.tsx
index 760def6..654a3a8 100644
--- a/src/components/image-converter.tsx
+++ b/src/components/image-converter.tsx
@@ -36,16 +36,19 @@ import {
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip";
-
-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" },
-];
+import { useTranslations } from "next-intl";
export function ImageConverter() {
+ const t = useTranslations("ImageConverter");
+
+ const aspectRatios = [
+ { name: t("customAspectRatio"), value: "custom" },
+ { name: t("squareAspectRatio"), value: "1/1" },
+ { name: t("standardAspectRatio"), value: "4/3" },
+ { name: t("photoAspectRatio"), value: "3/2" },
+ { name: t("widescreenAspectRatio"), value: "16/9" },
+ ];
+
const [images, setImages] = useState([]);
const [previewUrls, setPreviewUrls] = useState([]);
const [filenames, setFilenames] = useState([]);
@@ -86,7 +89,7 @@ export function ImageConverter() {
);
if (imageFiles.length === 0) {
- toast.error("No valid image files found.");
+ toast.error(t("toasts.noValidImages"));
return;
}
@@ -108,7 +111,7 @@ export function ImageConverter() {
setPreviewUrls(newPreviewUrls);
setFilenames(newFilenames);
- toast.success(`${imageFiles.length} image(s) added.`);
+ toast.success(t("toasts.imagesAdded", { count: imageFiles.length }));
};
const handleImageChange = (e: ChangeEvent) => {
@@ -148,7 +151,7 @@ export function ImageConverter() {
setImages([]);
setPreviewUrls([]);
setFilenames([]);
- toast.info("All images cleared.");
+ toast.info(t("toasts.allCleared"));
};
const handleFilenameChange = (index: number, newName: string) => {
@@ -260,12 +263,12 @@ export function ImageConverter() {
const handleConvertAndDownloadAll = async () => {
if (images.length === 0) {
- toast.error("Please upload images first.");
+ toast.error(t("toasts.noImages"));
return;
}
setIsConverting(true);
- toast.info(`Starting conversion for ${images.length} images...`);
+ toast.info(t("toasts.conversionStarting", { count: images.length }));
const conversionPromises = images.map((image, index) =>
convertAndDownload(image, previewUrls[index], index)
@@ -273,12 +276,12 @@ export function ImageConverter() {
try {
await Promise.all(conversionPromises);
- toast.success(`Successfully exported all ${images.length} images!`);
+ toast.success(t("toasts.conversionSuccess", { count: images.length }));
} catch (error) {
if (error instanceof Error) {
toast.error(error.message);
} else {
- toast.error("An unknown error occurred during conversion.");
+ toast.error(t("toasts.conversionError"));
}
} finally {
setIsConverting(false);
@@ -287,16 +290,16 @@ export function ImageConverter() {
const handleConvertAndDownloadSingle = async (index: number) => {
setConvertingIndex(index);
- toast.info(`Starting conversion for ${filenames[index]}...`);
+ toast.info(t("toasts.singleConversionStarting", { filename: filenames[index] }));
try {
await convertAndDownload(images[index], previewUrls[index], index);
- toast.success(`Successfully exported ${filenames[index]}!`);
+ toast.success(t("toasts.singleConversionSuccess", { filename: filenames[index] }));
} catch (error) {
if (error instanceof Error) {
toast.error(error.message);
} else {
- toast.error("An unknown error occurred during conversion.");
+ toast.error(t("toasts.conversionError"));
}
} finally {
setConvertingIndex(null);
@@ -304,7 +307,7 @@ export function ImageConverter() {
};
const handleApplySettings = () => {
- toast.info("Settings updated and will be used for all downloads.");
+ toast.info(t("toasts.settingsApplied"));
};
const handleAspectRatioChange = (value: string) => {
@@ -352,16 +355,16 @@ export function ImageConverter() {
const handleApplyDefaultBaseNameToAll = () => {
if (!defaultBaseName) {
- toast.error("Please enter a default base name to apply.");
+ toast.error(t("toasts.noDefaultBaseName"));
return;
}
if (!hasImages) {
- toast.info("Upload some images first.");
+ toast.info(t("toasts.uploadImagesFirst"));
return;
}
const newFilenames = filenames.map(() => defaultBaseName);
setFilenames(newFilenames);
- toast.success(`Set base name to "${defaultBaseName}" for all ${images.length} images.`);
+ toast.success(t("toasts.baseNameApplied", { baseName: defaultBaseName, count: images.length }));
};
return (
@@ -372,9 +375,9 @@ export function ImageConverter() {
-
Image Settings
+
{t("imageSettingsTitle")}
- Adjust resolution and scaling for all images.
+ {t("imageSettingsDescription")}
@@ -382,19 +385,19 @@ export function ImageConverter() {
-
+
- Choose a preset aspect ratio or select 'Custom' to enter dimensions manually.
+ {t("aspectRatioTooltip")}