feat(uploads): HEIC/HEIF support + dynamic format hint on guest upload (#821)

Two of the three things from #821:

- HEIC/HEIF (iPhone) can now be enabled. Sharp's bundled libvips decodes `heif`
  input (verified: sharp.format.heif.input.file === true on 0.34.3 / libvips
  8.17.1), so thumbnails generate. Added heic/heif to EXTENSION_TO_MIME in both
  the backend (uploadSettings.js) and the frontend (fileTypes.ts) maps, which
  are kept in sync. (iOS Safari usually transcodes HEIC→JPEG at file selection,
  but a genuine .heic upload is now handled when it arrives.)

- The upload requirements hint no longer hardcodes "JPEG, PNG or WebP". New
  extensionsToLabel() renders the actually-configured, supported formats (e.g.
  "JPG, PNG, WEBP, MOV"), and upload.fileRequirements interpolates {{formats}}
  across all 8 locales. Unsupported extensions are dropped from the label so it
  never advertises a format the backend would reject.

DNG / camera RAW is deliberately NOT included: Sharp's libvips has no raw loader,
so a DNG would upload then fail thumbnailing (photo → 'failed', no preview).
Proper RAW support (embedded-preview extraction) is a separate PR.

Adds vitest coverage for extensionsToLabel + the HEIC mapping.
This commit is contained in:
Paul Nothaft
2026-07-17 21:39:34 +02:00
parent 2a0361a83b
commit 2b5b23b96f
12 changed files with 81 additions and 10 deletions
+1 -1
View File
@@ -159,7 +159,7 @@
"noCategory": "Keine Kategorie",
"eventSpecific": "(Veranstaltungsspezifisch)",
"clickToUpload": "Klicken zum Hochladen oder per Drag & Drop",
"fileRequirements": "JPEG, PNG oder WebP (max. {{sizeLimit}}MB pro Datei, {{limit}} Dateien pro Upload)",
"fileRequirements": "{{formats}} (max. {{sizeLimit}}MB pro Datei, {{limit}} Dateien pro Upload)",
"selectedFiles": "Ausgewählte Dateien",
"uploading": "Wird hochgeladen...",
"transferring": "Übertragung",
+1 -1
View File
@@ -159,7 +159,7 @@
"noCategory": "No category",
"eventSpecific": "(Event specific)",
"clickToUpload": "Click to upload or drag and drop",
"fileRequirements": "JPEG, PNG or WebP (max {{sizeLimit}}MB per file, {{limit}} files per upload)",
"fileRequirements": "{{formats}} (max {{sizeLimit}}MB per file, {{limit}} files per upload)",
"selectedFiles": "Selected files",
"uploading": "Uploading...",
"transferring": "Transferring",
+1 -1
View File
@@ -126,7 +126,7 @@
"noCategory": "Sin categoría",
"eventSpecific": "(Específico del evento)",
"clickToUpload": "Haz clic para subir o arrastra y suelta",
"fileRequirements": "JPEG, PNG o WebP (máx. {{sizeLimit}}MB por archivo, {{limit}} archivos por subida)",
"fileRequirements": "{{formats}} (máx. {{sizeLimit}}MB por archivo, {{limit}} archivos por subida)",
"fileRequirementsMedia": "Imágenes JPEG, PNG o WebP y videos MP4/MOV/WEBM (máx. 50MB por archivo, {{limit}} archivos por subida)",
"unsupportedFiles": "Algunos archivos se omitieron porque el formato no es compatible (usa JPEG/PNG/WebP/MP4/MOV/WEBM).",
"selectedFiles": "Archivos seleccionados",
+1 -1
View File
@@ -131,7 +131,7 @@
"noCategory": "Aucune catégorie",
"eventSpecific": "(Spécifique à l'événement)",
"clickToUpload": "Cliquez pour téléverser ou glissez-déposez",
"fileRequirements": "JPEG, PNG ou WebP (max {{sizeLimit}} Mo par fichier, {{limit}} fichiers par téléversement)",
"fileRequirements": "{{formats}} (max {{sizeLimit}} Mo par fichier, {{limit}} fichiers par téléversement)",
"selectedFiles": "Fichiers sélectionnés",
"uploading": "Téléversement en cours...",
"transferring": "Transfert en cours",
+1 -1
View File
@@ -131,7 +131,7 @@
"noCategory": "Geen categorie",
"eventSpecific": "(Evenement-specifiek)",
"clickToUpload": "Klik om te uploaden of sleep bestanden hierheen",
"fileRequirements": "JPEG, PNG of WebP (max. {{sizeLimit}} MB per bestand, {{limit}} bestanden per upload)",
"fileRequirements": "{{formats}} (max. {{sizeLimit}} MB per bestand, {{limit}} bestanden per upload)",
"selectedFiles": "Geselecteerde bestanden",
"uploading": "Uploaden...",
"uploadComplete": "Upload voltooid!",
+1 -1
View File
@@ -131,7 +131,7 @@
"noCategory": "Sem categoria",
"eventSpecific": "(Específico do evento)",
"clickToUpload": "Clique para enviar ou arraste e solte",
"fileRequirements": "JPEG, PNG ou WebP (máx. {{sizeLimit}}MB por arquivo, {{limit}} arquivos por envio)",
"fileRequirements": "{{formats}} (máx. {{sizeLimit}}MB por arquivo, {{limit}} arquivos por envio)",
"selectedFiles": "Arquivos selecionados",
"uploading": "Enviando...",
"uploadComplete": "Envio concluído!",
+1 -1
View File
@@ -131,7 +131,7 @@
"noCategory": "Без категории",
"eventSpecific": "(Для конкретного события)",
"clickToUpload": "Нажмите для загрузки или перетащите файлы",
"fileRequirements": "JPEG, PNG или WebP (макс. {{sizeLimit}} МБ на файл, {{limit}} файлов за загрузку)",
"fileRequirements": "{{formats}} (макс. {{sizeLimit}} МБ на файл, {{limit}} файлов за загрузку)",
"selectedFiles": "Выбранные файлы",
"uploading": "Загрузка...",
"uploadComplete": "Загрузка завершена!",
+1 -1
View File
@@ -131,7 +131,7 @@
"noCategory": "Brez kategorije",
"eventSpecific": "(specifično za dogodek)",
"clickToUpload": "Kliknite za nalaganje ali povlecite in spustite",
"fileRequirements": "JPEG, PNG ali WebP (največ {{sizeLimit}} MB na datoteko, {{limit}} datotek na nalaganje)",
"fileRequirements": "{{formats}} (največ {{sizeLimit}} MB na datoteko, {{limit}} datotek na nalaganje)",
"selectedFiles": "Izbrane datoteke",
"uploading": "Nalaganje...",
"transferring": "Prenašanje",