From c75839d3ea29610dc6f6c498537a99fbc05bcaf8 Mon Sep 17 00:00:00 2001 From: Paul Nothaft Date: Sun, 6 Sep 2026 21:13:37 +0200 Subject: [PATCH] fix(gallery): keep canvas rendering in the lightbox, render tiles as MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every tile, the hero and the folder covers switched to a when the per-event toggle was on or the protection level was `maximum`. A canvas pins a backing store of naturalWidth × naturalHeight × 4 bytes that the browser is not allowed to evict, and iOS Safari has a hard budget for canvas memory that fails silently when exceeded — blank tiles, no error, on exactly the browser the large-gallery report came from. A gallery is several hundred tiles and one lightbox image. What canvas buys on a thumbnail is a slightly harder right-click. What actually protects the images is server-side: the served file is watermarked and the download route refuses when downloads are off. The photographer who reported the large-gallery case, shipping to real clients, said the same and turned the global toggle off once it was about to reach their next gallery. So: tiles, hero and folder covers always render . The lightbox keeps both the per-event toggle and the `maximum` implication — one image, where the calculus is different. The toggle is now wired to the lightbox for the first time; before this it reached only the tiles, so the label that said "canvas rendering" turned every grid into canvases and left the lightbox alone. Labels in all four locales now say where it applies. `protectionLevel` was destructured in seven tile components only to feed that OR; those props and their pass-throughs go with it. The shared layout props keep it, since the story layout still hands it to its lightbox. A source-level test pins that only PhotoLightbox passes useCanvasRendering to AuthenticatedImage or turns it on for `maximum`. Relates to issue 1287 --- .../components/gallery/GalleryFolderTiles.tsx | 8 --- .../src/components/gallery/GalleryView.tsx | 2 - .../src/components/gallery/HeroHeader.tsx | 5 -- frontend/src/components/gallery/PhotoGrid.tsx | 5 +- .../gallery/PhotoGridWithLayouts.tsx | 2 - .../__tests__/canvasLightboxOnly.test.ts | 56 +++++++++++++++++++ .../gallery/layouts/BaseGalleryLayout.tsx | 1 + .../gallery/layouts/GalleryPremiumLayout.tsx | 9 --- .../gallery/layouts/GalleryStoryLayout.tsx | 6 -- .../gallery/layouts/GridGalleryLayout.tsx | 9 --- .../layouts/JustifiedGalleryLayout.tsx | 10 ---- .../gallery/layouts/story/StoryCarousel.tsx | 6 -- .../gallery/layouts/story/StoryHero.tsx | 5 -- .../gallery/layouts/story/StoryPhotoCard.tsx | 5 -- .../settings/tabs/ImageSecurityTab.tsx | 2 +- frontend/src/i18n/locales/de.json | 4 +- frontend/src/i18n/locales/en.json | 4 +- frontend/src/i18n/locales/fr.json | 4 +- frontend/src/i18n/locales/sl.json | 4 +- .../event-details/EventInformationCard.tsx | 2 +- 20 files changed, 68 insertions(+), 81 deletions(-) create mode 100644 frontend/src/components/gallery/__tests__/canvasLightboxOnly.test.ts diff --git a/frontend/src/components/gallery/GalleryFolderTiles.tsx b/frontend/src/components/gallery/GalleryFolderTiles.tsx index 3ac15763..8906d6c3 100644 --- a/frontend/src/components/gallery/GalleryFolderTiles.tsx +++ b/frontend/src/components/gallery/GalleryFolderTiles.tsx @@ -22,9 +22,7 @@ interface GalleryFolderTilesProps { * so a gallery configured for canvas rendering or maximum protection must not * get an ordinary blob-backed here just because it is a cover. */ - protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; allowDownloads?: boolean; /** * Compact chip row instead of cover cards. Used by the full-bleed layouts @@ -40,8 +38,6 @@ export const GalleryFolderTiles: React.FC = ({ onOpen, compact = false, slug, - protectionLevel, - useCanvasRendering, }) => { const { t } = useTranslation(); @@ -95,10 +91,6 @@ export const GalleryFolderTiles: React.FC = ({ isGallery slug={slug} // Same rule as every other gallery image path: maximum - // protection implies canvas rendering even when the separate - // toggle is off (its default), otherwise a cover silently - // falls back to a blob-backed . - useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'} /> ) : (
diff --git a/frontend/src/components/gallery/GalleryView.tsx b/frontend/src/components/gallery/GalleryView.tsx index 56b0c6a8..b5273206 100644 --- a/frontend/src/components/gallery/GalleryView.tsx +++ b/frontend/src/components/gallery/GalleryView.tsx @@ -1311,9 +1311,7 @@ export const GalleryView: React.FC = ({ slug, event, requiresP onOpen={openFolderBySlug} compact={compact} slug={slug} - protectionLevel={protectionLevel} useEnhancedProtection={protectionLevel !== 'basic'} - useCanvasRendering={useCanvasRendering} allowDownloads={allowDownloads} /> ); diff --git a/frontend/src/components/gallery/HeroHeader.tsx b/frontend/src/components/gallery/HeroHeader.tsx index 6f183799..ec187f81 100644 --- a/frontend/src/components/gallery/HeroHeader.tsx +++ b/frontend/src/components/gallery/HeroHeader.tsx @@ -23,9 +23,7 @@ interface HeroHeaderProps { heroLogoPosition?: 'top' | 'center' | 'bottom'; dividerStyle?: HeroDividerStyle; allowDownloads?: boolean; - protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; onScrollToContent?: () => void; // Hero image anchor position (#162) – keyword or "X% Y%" focal point heroImageAnchor?: string; @@ -43,8 +41,6 @@ export const HeroHeader: React.FC = ({ heroLogoSize = 'medium', heroLogoPosition = 'top', dividerStyle = 'wave', - protectionLevel = 'standard', - useCanvasRendering = false, onScrollToContent, heroImageAnchor = 'center' }) => { @@ -142,7 +138,6 @@ export const HeroHeader: React.FC = ({ style={{ objectPosition: heroImageAnchor }} isGallery={true} slug={slug} - useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'} /> {/* Overlay */} diff --git a/frontend/src/components/gallery/PhotoGrid.tsx b/frontend/src/components/gallery/PhotoGrid.tsx index 154e8858..e41021db 100644 --- a/frontend/src/components/gallery/PhotoGrid.tsx +++ b/frontend/src/components/gallery/PhotoGrid.tsx @@ -196,7 +196,6 @@ export const PhotoGrid: React.FC = ({ allowDownloads={allowDownloads} protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} slug={slug} feedbackEnabled={feedbackEnabled} /> @@ -214,6 +213,7 @@ export const PhotoGrid: React.FC = ({ allowDownloads={allowDownloads} protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} + useCanvasRendering={useCanvasRendering} disableRightClick={disableRightClick} enableDevtoolsProtection={enableDevtoolsProtection} /> @@ -231,7 +231,6 @@ interface PhotoThumbnailProps { allowDownloads?: boolean; protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; slug: string; // Add slug as required prop feedbackEnabled?: boolean; } @@ -244,7 +243,6 @@ const PhotoThumbnail: React.FC = ({ onDownload, allowDownloads = true, protectionLevel = 'standard', - useCanvasRendering = false, slug, feedbackEnabled = false }) => { @@ -268,7 +266,6 @@ const PhotoThumbnail: React.FC = ({ loading="lazy" isGallery={true} slug={slug} - useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'} onProtectionViolation={(violationType) => { // Track analytics if (typeof window !== 'undefined' && (window as any).umami) { diff --git a/frontend/src/components/gallery/PhotoGridWithLayouts.tsx b/frontend/src/components/gallery/PhotoGridWithLayouts.tsx index 62a95481..b0ea6459 100644 --- a/frontend/src/components/gallery/PhotoGridWithLayouts.tsx +++ b/frontend/src/components/gallery/PhotoGridWithLayouts.tsx @@ -361,9 +361,7 @@ export const PhotoGridWithLayouts: React.FC = ({ heroLogoPosition={heroLogoPosition} dividerStyle={heroDividerStyle} allowDownloads={allowDownloads} - protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} heroImageAnchor={heroImageAnchor} /> )} diff --git a/frontend/src/components/gallery/__tests__/canvasLightboxOnly.test.ts b/frontend/src/components/gallery/__tests__/canvasLightboxOnly.test.ts new file mode 100644 index 00000000..d8a8c45e --- /dev/null +++ b/frontend/src/components/gallery/__tests__/canvasLightboxOnly.test.ts @@ -0,0 +1,56 @@ +/** + * Canvas rendering is a lightbox concern, not a tile concern. + * + * A canvas pins a backing store of naturalWidth × naturalHeight × 4 bytes + * that the browser is not allowed to evict, and iOS Safari has a hard + * budget for canvas memory that fails silently when exceeded — blank + * tiles, no error. A gallery is hundreds of tiles and one lightbox image, + * so the tiles render whatever the protection level says, and the + * lightbox keeps the per-event toggle and the `maximum` implication. + * + * Source-level pin: nothing under components/gallery except PhotoLightbox + * may hand `useCanvasRendering` to AuthenticatedImage. + */ +import fs from 'fs'; +import path from 'path'; +import { describe, it, expect } from 'vitest'; + +const root = path.resolve(__dirname, '..'); +function walk(dir: string): string[] { + return fs.readdirSync(dir, { withFileTypes: true }).flatMap((entry) => { + const file = path.join(dir, entry.name); + if (entry.isDirectory()) return entry.name === '__tests__' ? [] : walk(file); + return file.endsWith('.tsx') ? [file] : []; + }); +} + +describe('canvas rendering stays in the lightbox', () => { + const files = walk(root); + const lightbox = path.join(root, 'PhotoLightbox.tsx'); + + /** The JSX props of every in a file, plus every + * `imageProps={{ ... }}` object a layout hands to PhotoCard to spread in. */ + const imageProps = (source: string) => [ + ...source.split(' chunk.split('/>')[0]), + ...source.split('imageProps={{').slice(1).map((chunk) => chunk.split('}}')[0]), + ]; + + it('only PhotoLightbox passes useCanvasRendering to AuthenticatedImage', () => { + const offenders = files.filter((file) => file !== lightbox + && imageProps(fs.readFileSync(file, 'utf8')).some((props) => props.includes('useCanvasRendering'))); + expect(offenders.map((f) => path.relative(root, f))).toEqual([]); + // The pin has teeth: the lightbox itself is caught by the same probe. + expect(imageProps(fs.readFileSync(lightbox, 'utf8')).some((props) => props.includes('useCanvasRendering'))).toBe(true); + }); + + it('only PhotoLightbox turns canvas on for protection level maximum', () => { + const offenders = files.filter((file) => file !== lightbox + && /useCanvasRendering[^\n]*protectionLevel === 'maximum'/.test(fs.readFileSync(file, 'utf8'))); + expect(offenders.map((f) => path.relative(root, f))).toEqual([]); + }); + + it('the lightbox still does both', () => { + const source = fs.readFileSync(lightbox, 'utf8'); + expect(source).toMatch(/useCanvasRendering=\{useCanvasRendering \|\| protectionLevel === 'maximum'\}/); + }); +}); diff --git a/frontend/src/components/gallery/layouts/BaseGalleryLayout.tsx b/frontend/src/components/gallery/layouts/BaseGalleryLayout.tsx index e818d7b8..926ce009 100644 --- a/frontend/src/components/gallery/layouts/BaseGalleryLayout.tsx +++ b/frontend/src/components/gallery/layouts/BaseGalleryLayout.tsx @@ -47,6 +47,7 @@ export interface BaseGalleryLayoutProps { onPickResolution?: (photoIds: number[]) => void; protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; + /** Canvas rendering applies to the lightbox only; tiles always render . */ useCanvasRendering?: boolean; feedbackEnabled?: boolean; feedbackOptions?: { diff --git a/frontend/src/components/gallery/layouts/GalleryPremiumLayout.tsx b/frontend/src/components/gallery/layouts/GalleryPremiumLayout.tsx index e6c99b34..0a2dd95a 100644 --- a/frontend/src/components/gallery/layouts/GalleryPremiumLayout.tsx +++ b/frontend/src/components/gallery/layouts/GalleryPremiumLayout.tsx @@ -46,9 +46,7 @@ interface PhotoCardProps { isLiked: boolean; slug: string; allowDownloads?: boolean; - protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; feedbackEnabled?: boolean; // #506: track the per-event "allow likes" toggle so the per-photo // Like button respects it. `feedbackEnabled` alone isn't enough — @@ -68,8 +66,6 @@ const PhotoCard: React.FC = ({ isSelectionMode, isLiked, slug, - protectionLevel = 'standard', - useCanvasRendering = false, feedbackEnabled = false, allowLikes = false, index @@ -122,7 +118,6 @@ const PhotoCard: React.FC = ({ loading="lazy" isGallery={true} slug={slug} - useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'} /> {/* Colour label (#1044) — same badge every layout uses. */} @@ -208,9 +203,7 @@ export const GalleryPremiumLayout: React.FC = ({ allowDownloads = true, downloadChoices, onPickResolution, - protectionLevel = 'standard', useEnhancedProtection = false, - useCanvasRendering = false, feedbackEnabled = false, feedbackOptions, heroPhotoOverride, @@ -629,9 +622,7 @@ export const GalleryPremiumLayout: React.FC = ({ isLiked={likedPhotoIds.has(originalPhoto.id)} slug={slug} allowDownloads={allowDownloads} - protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} feedbackEnabled={feedbackEnabled} allowLikes={!!feedbackOptions?.allowLikes} index={photoIndex} diff --git a/frontend/src/components/gallery/layouts/GalleryStoryLayout.tsx b/frontend/src/components/gallery/layouts/GalleryStoryLayout.tsx index 6f91e82e..bc5f6511 100644 --- a/frontend/src/components/gallery/layouts/GalleryStoryLayout.tsx +++ b/frontend/src/components/gallery/layouts/GalleryStoryLayout.tsx @@ -255,9 +255,7 @@ export const GalleryStoryLayout: React.FC = ({ photo={heroPhoto} slug={slug} allowDownloads={allowDownloads} - protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} /> {/* Main Content - Scenes */} @@ -281,9 +279,7 @@ export const GalleryStoryLayout: React.FC = ({ onPhotoClick={handleOpenLightbox} slug={slug} allowDownloads={allowDownloads} - protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} /> ) : (
@@ -298,9 +294,7 @@ export const GalleryStoryLayout: React.FC = ({ slug={slug} galleryId={`gallery-${scene.id}`} allowDownloads={allowDownloads} - protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} // Mark first photo in each grid as featured featured={index === 0 && scene.photos.length > 4} /> diff --git a/frontend/src/components/gallery/layouts/GridGalleryLayout.tsx b/frontend/src/components/gallery/layouts/GridGalleryLayout.tsx index e10f5c6e..61f1d5d6 100644 --- a/frontend/src/components/gallery/layouts/GridGalleryLayout.tsx +++ b/frontend/src/components/gallery/layouts/GridGalleryLayout.tsx @@ -18,9 +18,7 @@ interface GridPhotoProps { animationType?: string; allowDownloads?: boolean; slug?: string; - protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; feedbackEnabled?: boolean; feedbackOptions?: { allowLikes?: boolean; @@ -47,8 +45,6 @@ const GridPhoto: React.FC = ({ animationType = 'fade', allowDownloads = true, slug, - protectionLevel = 'standard', - useCanvasRendering = false, feedbackEnabled = false, feedbackOptions, savedIdentity, @@ -130,7 +126,6 @@ const GridPhoto: React.FC = ({ loading: 'lazy', isGallery: true, slug, - useCanvasRendering: useCanvasRendering || protectionLevel === 'maximum', onProtectionViolation: (violationType: string) => { console.warn(`Protection violation on grid photo ${photo.id}: ${violationType}`); }, @@ -200,9 +195,7 @@ export const GridGalleryLayout: React.FC = ({ isSelectionMode = false, onPhotoSelect, allowDownloads = true, - protectionLevel = 'standard', useEnhancedProtection = false, - useCanvasRendering = false, feedbackEnabled = false, feedbackOptions, isClient = false, @@ -255,9 +248,7 @@ export const GridGalleryLayout: React.FC = ({ animationType={animation} allowDownloads={allowDownloads} slug={slug} - protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} feedbackEnabled={feedbackEnabled} feedbackOptions={feedbackOptions} savedIdentity={savedIdentity} diff --git a/frontend/src/components/gallery/layouts/JustifiedGalleryLayout.tsx b/frontend/src/components/gallery/layouts/JustifiedGalleryLayout.tsx index 8e0d5113..b4f3295d 100644 --- a/frontend/src/components/gallery/layouts/JustifiedGalleryLayout.tsx +++ b/frontend/src/components/gallery/layouts/JustifiedGalleryLayout.tsx @@ -40,9 +40,7 @@ interface JustifiedPhotoProps { animationType?: string; allowDownloads?: boolean; slug?: string; - protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; feedbackEnabled?: boolean; feedbackOptions?: { allowLikes?: boolean; @@ -69,8 +67,6 @@ const JustifiedPhoto: React.FC = ({ animationType = 'fade', allowDownloads = true, slug, - protectionLevel = 'standard', - useCanvasRendering = false, feedbackEnabled = false, feedbackOptions, savedIdentity, @@ -133,7 +129,6 @@ const JustifiedPhoto: React.FC = ({ loading: 'lazy', isGallery: true, slug, - useCanvasRendering: useCanvasRendering || protectionLevel === 'maximum', onProtectionViolation: (violationType: string) => { console.warn(`Protection violation on justified photo ${photo.id}: ${violationType}`); }, @@ -214,9 +209,7 @@ export const JustifiedGalleryLayout: React.FC = ({ isSelectionMode = false, onPhotoSelect, allowDownloads = true, - protectionLevel = 'standard', useEnhancedProtection = false, - useCanvasRendering = false, feedbackEnabled = false, feedbackOptions, // Hero props @@ -399,7 +392,6 @@ export const JustifiedGalleryLayout: React.FC = ({ className="w-full h-full object-cover" isGallery={true} slug={slug} - useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'} /> {/* Overlay */} @@ -523,9 +515,7 @@ export const JustifiedGalleryLayout: React.FC = ({ animationType={animation} allowDownloads={allowDownloads} slug={slug} - protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} feedbackEnabled={feedbackEnabled} feedbackOptions={feedbackOptions} savedIdentity={savedIdentity} diff --git a/frontend/src/components/gallery/layouts/story/StoryCarousel.tsx b/frontend/src/components/gallery/layouts/story/StoryCarousel.tsx index d88212f8..c001e771 100644 --- a/frontend/src/components/gallery/layouts/story/StoryCarousel.tsx +++ b/frontend/src/components/gallery/layouts/story/StoryCarousel.tsx @@ -14,9 +14,7 @@ interface StoryCarouselProps { slug: string; id: string; allowDownloads?: boolean; - protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; } export const StoryCarousel: React.FC = ({ @@ -27,9 +25,7 @@ export const StoryCarousel: React.FC = ({ slug, id, allowDownloads = true, - protectionLevel = 'standard', useEnhancedProtection = false, - useCanvasRendering = false }) => { return (
@@ -53,9 +49,7 @@ export const StoryCarousel: React.FC = ({ slug={slug} galleryId={id} allowDownloads={allowDownloads} - protectionLevel={protectionLevel} useEnhancedProtection={useEnhancedProtection} - useCanvasRendering={useCanvasRendering} />
diff --git a/frontend/src/components/gallery/layouts/story/StoryHero.tsx b/frontend/src/components/gallery/layouts/story/StoryHero.tsx index 66293623..6c56b841 100644 --- a/frontend/src/components/gallery/layouts/story/StoryHero.tsx +++ b/frontend/src/components/gallery/layouts/story/StoryHero.tsx @@ -11,9 +11,7 @@ interface StoryHeroProps { photo?: Photo | null; slug: string; allowDownloads?: boolean; - protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; } export const StoryHero: React.FC = ({ @@ -22,8 +20,6 @@ export const StoryHero: React.FC = ({ stats, photo, slug, - protectionLevel = 'standard', - useCanvasRendering = false }) => { const formattedDate = date ? new Date(date).toLocaleDateString('en-US', { @@ -49,7 +45,6 @@ export const StoryHero: React.FC = ({ className="w-full h-full object-cover" isGallery={true} slug={slug} - useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'} /> ) : (
diff --git a/frontend/src/components/gallery/layouts/story/StoryPhotoCard.tsx b/frontend/src/components/gallery/layouts/story/StoryPhotoCard.tsx index a5eb5643..2cfeee02 100644 --- a/frontend/src/components/gallery/layouts/story/StoryPhotoCard.tsx +++ b/frontend/src/components/gallery/layouts/story/StoryPhotoCard.tsx @@ -14,9 +14,7 @@ interface StoryPhotoCardProps { onClick?: () => void; slug: string; allowDownloads?: boolean; - protectionLevel?: 'basic' | 'standard' | 'enhanced' | 'maximum'; useEnhancedProtection?: boolean; - useCanvasRendering?: boolean; featured?: boolean; galleryId: string; } @@ -28,8 +26,6 @@ export const StoryPhotoCard: React.FC = ({ onToggleFavorite, onClick, slug, - protectionLevel = 'standard', - useCanvasRendering = false, featured = false, galleryId: _galleryId }) => { @@ -103,7 +99,6 @@ export const StoryPhotoCard: React.FC = ({ }`} isGallery={true} slug={slug} - useCanvasRendering={useCanvasRendering || protectionLevel === 'maximum'} /> )} diff --git a/frontend/src/features/settings/tabs/ImageSecurityTab.tsx b/frontend/src/features/settings/tabs/ImageSecurityTab.tsx index 7c2a3354..86ccd266 100644 --- a/frontend/src/features/settings/tabs/ImageSecurityTab.tsx +++ b/frontend/src/features/settings/tabs/ImageSecurityTab.tsx @@ -184,7 +184,7 @@ export const ImageSecurityTab: React.FC = () => { /> - {t('settings.imageSecurity.enableCanvas', 'Enable canvas rendering by default (advanced protection)')} + {t('settings.imageSecurity.enableCanvas', 'Enable canvas rendering in the lightbox by default (advanced protection)')}
diff --git a/frontend/src/i18n/locales/de.json b/frontend/src/i18n/locales/de.json index 0f3567c4..3eef43b2 100644 --- a/frontend/src/i18n/locales/de.json +++ b/frontend/src/i18n/locales/de.json @@ -1934,7 +1934,7 @@ "disableRightClick": "Rechtsklick-Menü blockieren", "watermarkDownloads": "Wasserzeichen bei Downloads hinzufügen", "enableDevtoolsProtection": "Entwicklertools erkennen", - "useCanvasRendering": "Canvas-Rendering (erweiterter Schutz)", + "useCanvasRendering": "Canvas-Rendering in der Lightbox (erweiterter Schutz)", "protectionInfo": "Schutzfunktionen helfen, unerlaubte Downloads zu verhindern, können jedoch nicht alle Methoden blockieren.", "protectionLevelBasic": "Einfach - Nur Rechtsklick-Sperre", "protectionLevelStandard": "Standard - Tastenkombinationen blockiert", @@ -2374,7 +2374,7 @@ "protectionLevel": "Standard-Schutzstufe", "imageQuality": "Standard-Bildqualität", "enableDevtools": "DevTools-Erkennung standardmäßig aktivieren", - "enableCanvas": "Canvas-Rendering standardmäßig aktivieren (erweiterter Schutz)", + "enableCanvas": "Canvas-Rendering in der Lightbox standardmäßig aktivieren (erweiterter Schutz)", "rateLimiting": "Ratenbegrenzung", "rateLimitingHelp": "Begrenzen Sie, wie viele Bilder angefordert werden können, um Scraping zu verhindern.", "requestsPerMinute": "Anfragen pro Minute", diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 4b300b5c..61df73c0 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -1430,7 +1430,7 @@ "disableRightClick": "Block right-click menu", "watermarkDownloads": "Add watermark to downloads", "enableDevtoolsProtection": "Detect developer tools", - "useCanvasRendering": "Canvas rendering (advanced protection)", + "useCanvasRendering": "Canvas rendering in the lightbox (advanced protection)", "protectionInfo": "Protection features help prevent unauthorized downloads but cannot block all methods.", "protectionLevelBasic": "Basic - Right-click blocking only", "protectionLevelStandard": "Standard - Keyboard shortcuts blocked", @@ -1937,7 +1937,7 @@ "protectionLevel": "Default Protection Level", "imageQuality": "Default Image Quality", "enableDevtools": "Enable DevTools detection by default", - "enableCanvas": "Enable canvas rendering by default (advanced protection)", + "enableCanvas": "Enable canvas rendering in the lightbox by default (advanced protection)", "rateLimiting": "Rate Limiting", "rateLimitingHelp": "Limit how many images can be requested to prevent scraping.", "requestsPerMinute": "Requests per minute", diff --git a/frontend/src/i18n/locales/fr.json b/frontend/src/i18n/locales/fr.json index f18625db..03c7b368 100644 --- a/frontend/src/i18n/locales/fr.json +++ b/frontend/src/i18n/locales/fr.json @@ -498,7 +498,7 @@ "disableRightClick": "Bloquer le clic droit", "watermarkDownloads": "Ajouter un filigrane aux téléchargements", "enableDevtoolsProtection": "Détecter les outils de développement", - "useCanvasRendering": "Rendu canvas (protection avancée)", + "useCanvasRendering": "Rendu canvas dans la visionneuse (protection avancée)", "protectionInfo": "Les fonctionnalités de protection aident à prévenir les téléchargements non autorisés mais ne peuvent pas bloquer toutes les méthodes.", "protectionLevelBasic": "Basique - Blocage du clic droit uniquement", "protectionLevelStandard": "Standard - Blocage des raccourcis clavier", @@ -912,7 +912,7 @@ "protectionLevel": "Niveau de protection par défaut", "imageQuality": "Qualité d'image par défaut", "enableDevtools": "Activer la détection des outils DevTools par défaut", - "enableCanvas": "Activer le rendu canvas par défaut (protection avancée)", + "enableCanvas": "Activer le rendu canvas dans la visionneuse par défaut (protection avancée)", "rateLimiting": "Limitation de débit", "rateLimitingHelp": "Limitez le nombre d'images qui peuvent être demandées pour éviter le scraping.", "requestsPerMinute": "Requêtes par minute", diff --git a/frontend/src/i18n/locales/sl.json b/frontend/src/i18n/locales/sl.json index a02444bc..c13ca419 100644 --- a/frontend/src/i18n/locales/sl.json +++ b/frontend/src/i18n/locales/sl.json @@ -498,7 +498,7 @@ "disableRightClick": "Blokiraj meni desnega klika", "watermarkDownloads": "Dodaj vodni žig prenosom", "enableDevtoolsProtection": "Zaznaj orodja za razvijalce", - "useCanvasRendering": "Izris prek canvas (napredna zaščita)", + "useCanvasRendering": "Izris prek canvas v pregledovalniku (napredna zaščita)", "protectionInfo": "Zaščitne funkcije pomagajo preprečiti nepooblaščene prenose, vendar ne morejo blokirati vseh metod.", "protectionLevelBasic": "Osnovno - samo blokada desnega klika", "protectionLevelStandard": "Standardno - blokirane bližnjice na tipkovnici", @@ -912,7 +912,7 @@ "protectionLevel": "Privzeta raven zaščite", "imageQuality": "Privzeta kakovost slike", "enableDevtools": "Privzeto omogoči zaznavanje DevTools", - "enableCanvas": "Privzeto omogoči izris prek canvas (napredna zaščita)", + "enableCanvas": "Privzeto omogoči izris prek canvas v pregledovalniku (napredna zaščita)", "rateLimiting": "Omejevanje hitrosti", "rateLimitingHelp": "Omejite, koliko slik je mogoče zahtevati, da preprečite množično pobiranje.", "requestsPerMinute": "Zahtev na minuto", diff --git a/frontend/src/pages/admin/event-details/EventInformationCard.tsx b/frontend/src/pages/admin/event-details/EventInformationCard.tsx index 38aa686e..6311b2be 100644 --- a/frontend/src/pages/admin/event-details/EventInformationCard.tsx +++ b/frontend/src/pages/admin/event-details/EventInformationCard.tsx @@ -669,7 +669,7 @@ export const EventInformationCard: React.FC = ({ className="w-4 h-4 text-accent border-neutral-300 dark:border-neutral-600 rounded focus:ring-primary-500" /> - {t('events.useCanvasRendering', 'Canvas rendering (advanced protection)')} + {t('events.useCanvasRendering', 'Canvas rendering in the lightbox (advanced protection)')}