fix: handle non-image modalities across uploads, previews, and filenames (#255)

SnapOtter spans five modalities now, but several code paths still assumed image input.

- dropzone: default to accept-all when no fileFilter is given (image tools still pass one); neutral "supported file types" error text instead of "image files"
- automate (pipelines): accept any modality in the file pickers and dropzones; render modality-aware previews (video player, audio waveform, document/data card) instead of always using ImageViewer/BeforeAfterSlider
- filename sanitizer: extend the double-extension allowlist beyond image extensions to video/audio/document/data so e.g. "report.csv.php" becomes "report.csv"; add tests
- thumbnail route: return 422 for non-rasterisable files (audio, data, non-PDF docs) instead of attempting a doomed Sharp decode
- pool: unknown tools fall back to the "system" pool, not the image pool
- a11y labels: "Previous/Next image", "Image viewer/area/controls/drop zone" are now modality-neutral, across all 21 locales
- copy: bulk-rename default, find-duplicates ZIP name, SSRF user-agent, fetch-urls fallback name, file-details MIME label, URL-import placeholder, help dialog
This commit is contained in:
SnapOtter
2026-06-16 18:04:48 +08:00
committed by GitHub
parent 622c9f98a5
commit 8eee17aeea
29 changed files with 420 additions and 246 deletions
+68 -6
View File
@@ -1,6 +1,10 @@
import { basename } from "node:path"; import { basename } from "node:path";
const SAFE_IMAGE_EXTENSIONS = new Set([ // Recognised, safe file extensions across all five modalities (image, video,
// audio, document, data). Used to truncate double-extension attacks after the
// first known-good extension, e.g. "report.csv.php" becomes "report.csv".
const SAFE_EXTENSIONS = new Set([
// Image
".jpg", ".jpg",
".jpeg", ".jpeg",
".png", ".png",
@@ -11,7 +15,65 @@ const SAFE_IMAGE_EXTENSIONS = new Set([
".tif", ".tif",
".avif", ".avif",
".svg", ".svg",
".heic",
".heif",
".jxl",
".ico",
".jp2",
".qoi",
".psd",
".dng",
// Video
".mp4",
".webm",
".mov",
".mkv",
".avi",
".m4v",
".mpg",
".mpeg",
".wmv",
".flv",
".ogv",
// Audio
".mp3",
".wav",
".flac",
".ogg",
".oga",
".aac",
".m4a",
".opus",
".wma",
".aiff",
".aif",
// Document
".pdf", ".pdf",
".doc",
".docx",
".odt",
".rtf",
".txt",
".md",
".markdown",
".html",
".htm",
".epub",
".ppt",
".pptx",
".odp",
".xls",
".xlsx",
".ods",
// Data
".csv",
".tsv",
".json",
".xml",
".yaml",
".yml",
".zip",
".srt",
]); ]);
/** /**
@@ -19,8 +81,8 @@ const SAFE_IMAGE_EXTENSIONS = new Set([
* *
* 1. Strips directory separators (basename only). * 1. Strips directory separators (basename only).
* 2. Removes ".." sequences and null bytes. * 2. Removes ".." sequences and null bytes.
* 3. Truncates after the first recognised image extension so that * 3. Truncates after the first recognised file extension so that
* "photo.png.php" becomes "photo.png". * "report.csv.php" becomes "report.csv".
*/ */
export function sanitizeFilename(raw: string): string { export function sanitizeFilename(raw: string): string {
let name = basename(raw); let name = basename(raw);
@@ -30,14 +92,14 @@ export function sanitizeFilename(raw: string): string {
name = "upload"; name = "upload";
} }
// Guard against double-extension attacks (e.g. "image.png.php"). // Guard against double-extension attacks (e.g. "report.csv.php").
// Walk the dot-separated parts and truncate after the first safe image extension. // Walk the dot-separated parts and truncate after the first safe extension.
const dotIndex = name.indexOf("."); const dotIndex = name.indexOf(".");
if (dotIndex !== -1) { if (dotIndex !== -1) {
const parts = name.split("."); const parts = name.split(".");
for (let i = 1; i < parts.length; i++) { for (let i = 1; i < parts.length; i++) {
const ext = `.${parts[i].toLowerCase()}`; const ext = `.${parts[i].toLowerCase()}`;
if (SAFE_IMAGE_EXTENSIONS.has(ext)) { if (SAFE_EXTENSIONS.has(ext)) {
// Keep everything up to and including this extension, drop the rest // Keep everything up to and including this extension, drop the rest
name = parts.slice(0, i + 1).join("."); name = parts.slice(0, i + 1).join(".");
break; break;
+3 -1
View File
@@ -6,7 +6,9 @@ import type { Pool } from "../jobs/types.js";
export function resolveToolPool(toolId: string): Pool { export function resolveToolPool(toolId: string): Pool {
if (hasAiJobHandler(toolId) || TOOL_BUNDLE_MAP[toolId]) return "ai"; if (hasAiJobHandler(toolId) || TOOL_BUNDLE_MAP[toolId]) return "ai";
const tool = TOOLS.find((t) => t.id === toolId); const tool = TOOLS.find((t) => t.id === toolId);
return tool ? MODALITY_POOL[tool.modality] : "image"; // Unknown tools fall back to the general-purpose "system" pool rather than
// assuming the image pool (a legacy image-only default).
return tool ? MODALITY_POOL[tool.modality] : "system";
} }
export function shouldSkipSyncWindow(executionHint: "fast" | "long" | undefined): boolean { export function shouldSkipSyncWindow(executionHint: "fast" | "long" | undefined): boolean {
+2 -2
View File
@@ -158,7 +158,7 @@ export async function safeFetch(url: string, signal?: AbortSignal): Promise<Resp
signal, signal,
redirect: "manual", redirect: "manual",
headers: { headers: {
"User-Agent": "SnapOtter/1.0 (image-fetch)", "User-Agent": "SnapOtter/2.0 (file-fetch)",
Host: parsed.host, Host: parsed.host,
}, },
}; };
@@ -176,7 +176,7 @@ export async function safeFetch(url: string, signal?: AbortSignal): Promise<Resp
agent, agent,
signal: signal ?? undefined, signal: signal ?? undefined,
headers: { headers: {
"User-Agent": "SnapOtter/1.0 (image-fetch)", "User-Agent": "SnapOtter/2.0 (file-fetch)",
}, },
method: "GET", method: "GET",
}, },
+1 -1
View File
@@ -110,7 +110,7 @@ function filenameFromUrl(url: string): string {
} catch { } catch {
// ignore parse errors // ignore parse errors
} }
return `image-${randomUUID().slice(0, 8)}`; return `file-${randomUUID().slice(0, 8)}`;
} }
/** /**
+6
View File
@@ -524,6 +524,12 @@ export async function userFileRoutes(app: FastifyInstance): Promise<void> {
// Image thumbnail (existing path) // Image thumbnail (existing path)
const validation = await validateImageBuffer(rawBuffer, file.originalName); const validation = await validateImageBuffer(rawBuffer, file.originalName);
if (!validation.valid) {
// Audio, data, and non-PDF document files have no raster thumbnail.
// Return 422 (not 204) so the client's <img> onError falls back to a
// modality icon instead of attempting a doomed Sharp decode below.
return reply.status(422).send({ error: "No thumbnail available for this file type" });
}
let decoded: Buffer<ArrayBuffer> = Buffer.from(rawBuffer); let decoded: Buffer<ArrayBuffer> = Buffer.from(rawBuffer);
if (validation.valid && validation.format === "heif") { if (validation.valid && validation.format === "heif") {
decoded = Buffer.from(await decodeHeic(rawBuffer)); decoded = Buffer.from(await decodeHeic(rawBuffer));
+6 -3
View File
@@ -113,7 +113,10 @@ export function Dropzone({
acceptDescription, acceptDescription,
}: DropzoneProps) { }: DropzoneProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const checkFile = fileFilter ?? isImageFile; // When no explicit filter is given, accept any file: the file picker still
// restricts via `accept`, and the server validates per modality. Image tools
// pass an explicit fileFilter, so they are unaffected.
const checkFile = fileFilter ?? (() => true);
const resolvedAccept = expandAccept(accept); const resolvedAccept = expandAccept(accept);
const [isDragging, setIsDragging] = useState(false); const [isDragging, setIsDragging] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@@ -168,7 +171,7 @@ export function Dropzone({
if (validFiles.length > 0) { if (validFiles.length > 0) {
onFiles?.(validFiles); onFiles?.(validFiles);
} else if (droppedFiles.length > 0) { } else if (droppedFiles.length > 0) {
setError(acceptDescription || `This tool accepts ${accept || "image files"}`); setError(acceptDescription || `This tool accepts ${accept || "the supported file types"}`);
} }
}, },
[onFiles, checkFile, acceptDescription, accept], [onFiles, checkFile, acceptDescription, accept],
@@ -186,7 +189,7 @@ export function Dropzone({
if (validFiles.length > 0) { if (validFiles.length > 0) {
onFiles?.(validFiles); onFiles?.(validFiles);
} else if (picked.length > 0) { } else if (picked.length > 0) {
setError(acceptDescription || `This tool accepts ${accept || "image files"}`); setError(acceptDescription || `This tool accepts ${accept || "the supported file types"}`);
} }
}; };
input.click(); input.click();
@@ -136,7 +136,7 @@ export function UrlImportModal({ onClose, onImport }: UrlImportModalProps) {
value={text} value={text}
onChange={(e) => setText(e.target.value)} onChange={(e) => setText(e.target.value)}
placeholder={ placeholder={
"https://example.com/photo1.jpg\nhttps://example.com/photo2.png\n- https://example.com/photo3.webp\n[My image](https://example.com/photo4.jpg)" "https://example.com/photo.jpg\nhttps://example.com/report.pdf\n- https://example.com/clip.mp4\n[My file](https://example.com/recording.mp3)"
} }
className="w-full min-h-[120px] max-h-[240px] resize-y rounded-lg border border-border bg-muted px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50" className="w-full min-h-[120px] max-h-[240px] resize-y rounded-lg border border-border bg-muted px-3 py-2 text-sm font-mono text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-primary/50"
/> />
@@ -502,7 +502,7 @@ export function FileDetails({ mobile = false }: FileDetailsProps) {
<DetailRow label={t.files.name} value={details.originalName} /> <DetailRow label={t.files.name} value={details.originalName} />
<DetailRow <DetailRow
label={t.files.format} label={t.files.format}
value={details.mimeType.replace("image/", "").toUpperCase()} value={details.mimeType.split("/").pop()?.toUpperCase() ?? ""}
/> />
<DetailRow label={t.files.size} value={formatSize(details.size)} /> <DetailRow label={t.files.size} value={formatSize(details.size)} />
<DetailRow <DetailRow
+2 -2
View File
@@ -80,8 +80,8 @@ export function HelpDialog({ open, onClose }: HelpDialogProps) {
<h3 className="text-sm font-semibold">{t.help.gettingStarted.heading}</h3> <h3 className="text-sm font-semibold">{t.help.gettingStarted.heading}</h3>
</div> </div>
<p className="text-sm text-muted-foreground leading-relaxed"> <p className="text-sm text-muted-foreground leading-relaxed">
Select a tool from the sidebar or search for one with <Kbd keys="mod+k" />. Upload an Select a tool from the sidebar or search for one with <Kbd keys="mod+k" />. Upload a
image by dragging it onto the page or clicking the upload area. Adjust settings and file by dragging it onto the page or clicking the upload area. Adjust settings and
download your result. download your result.
</p> </p>
</section> </section>
@@ -7,7 +7,7 @@ import { useFileStore } from "@/stores/file-store";
export function BulkRenameSettings() { export function BulkRenameSettings() {
const { t } = useTranslation(); const { t } = useTranslation();
const { files, processing, error, setProcessing, setError } = useFileStore(); const { files, processing, error, setProcessing, setError } = useFileStore();
const [pattern, setPattern] = useState("image-{{index}}"); const [pattern, setPattern] = useState("file-{{index}}");
const [startIndex, setStartIndex] = useState(1); const [startIndex, setStartIndex] = useState(1);
const [downloadReady, setDownloadReady] = useState(false); const [downloadReady, setDownloadReady] = useState(false);
@@ -155,7 +155,7 @@ export function FindDuplicatesSettings() {
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const a = document.createElement("a"); const a = document.createElement("a");
a.href = url; a.href = url;
a.download = "unique-images.zip"; a.download = "unique-files.zip";
a.click(); a.click();
URL.revokeObjectURL(url); URL.revokeObjectURL(url);
}, [files, results, bestOverrides]); }, [files, results, bestOverrides]);
+119 -34
View File
@@ -5,6 +5,7 @@ import {
ChevronRight, ChevronRight,
ChevronUp, ChevronUp,
Download, Download,
FileImage,
FolderOpen, FolderOpen,
Layers, Layers,
Play, Play,
@@ -15,7 +16,7 @@ import {
Workflow, Workflow,
X, X,
} from "lucide-react"; } from "lucide-react";
import { useCallback, useEffect, useRef, useState } from "react"; import { lazy, Suspense, useCallback, useEffect, useRef, useState } from "react";
import { useLocation, useNavigate } from "react-router-dom"; import { useLocation, useNavigate } from "react-router-dom";
import { BeforeAfterSlider } from "@/components/common/before-after-slider"; import { BeforeAfterSlider } from "@/components/common/before-after-slider";
import { Dropzone } from "@/components/common/dropzone"; import { Dropzone } from "@/components/common/dropzone";
@@ -37,6 +38,13 @@ import { cn } from "@/lib/utils";
import { useFileStore } from "@/stores/file-store"; import { useFileStore } from "@/stores/file-store";
import { type SavedPipeline, usePipelineStore } from "@/stores/pipeline-store"; import { type SavedPipeline, usePipelineStore } from "@/stores/pipeline-store";
const MediaPlayerView = lazy(() =>
import("@/components/tools/media-player-view").then((m) => ({ default: m.MediaPlayerView })),
);
const WaveformPlayer = lazy(() =>
import("@/components/common/waveform-player").then((m) => ({ default: m.WaveformPlayer })),
);
export function AutomatePage() { export function AutomatePage() {
const { t } = useTranslation(); const { t } = useTranslation();
usePageTitle(t.sidebar.automate); usePageTitle(t.sidebar.automate);
@@ -167,7 +175,6 @@ export function AutomatePage() {
const input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.multiple = true; input.multiple = true;
input.accept = "image/*,.avif,.heic,.heif,.hif";
input.onchange = (e) => { input.onchange = (e) => {
const picked = Array.from((e.target as HTMLInputElement).files || []); const picked = Array.from((e.target as HTMLInputElement).files || []);
if (picked.length > 0) addFiles(picked); if (picked.length > 0) addFiles(picked);
@@ -361,6 +368,102 @@ export function AutomatePage() {
[addStep, isMobile], [addStep, isMobile],
); );
/**
* Render modality-aware preview for the pipeline result or source file.
* Mirrors the previewKind switching in tool-page.tsx: images get
* BeforeAfterSlider / ImageViewer, video gets MediaPlayerView, audio
* gets WaveformPlayer, and document/data get a static info card.
*/
function renderPipelinePreview(mode: "result" | "original") {
const kind = currentEntry?.previewKind ?? "image";
if (mode === "result") {
if (kind === "image") {
return (
<BeforeAfterSlider
beforeSrc={originalBlobUrl!}
afterSrc={processedUrl as string}
beforeSize={originalSize ?? undefined}
afterSize={processedSize ?? undefined}
/>
);
}
if (kind === "video") {
return (
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading...</div>}>
<MediaPlayerView />
</Suspense>
);
}
if (kind === "audio") {
return (
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading...</div>}>
<WaveformPlayer src={processedUrl as string} />
</Suspense>
);
}
// document / data: success card with filename + size
const fname = currentEntry?.processedFilename ?? selectedFileName ?? files[0]?.name ?? "file";
const fsize = processedSize ?? 0;
const ext = fname.split(".").pop()?.toUpperCase() ?? "";
return (
<div className="flex items-center justify-center h-full">
<div className="text-center p-6 max-w-xs">
<div className="mx-auto w-14 h-14 rounded-2xl bg-emerald-50 dark:bg-emerald-950/30 flex items-center justify-center mb-3">
<CheckCircle2 className="h-7 w-7 text-emerald-600 dark:text-emerald-400" />
</div>
<p className="font-medium text-foreground mb-1">{fname}</p>
<p className="text-xs text-muted-foreground">
{ext} &middot; {formatFileSize(fsize)}
</p>
</div>
</div>
);
}
// mode === "original"
if (kind === "image") {
return (
<ImageViewer
src={originalBlobUrl!}
filename={selectedFileName ?? files[0].name}
fileSize={selectedFileSize ?? files[0].size}
/>
);
}
if (kind === "video") {
return (
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading...</div>}>
<MediaPlayerView />
</Suspense>
);
}
if (kind === "audio") {
return (
<Suspense fallback={<div className="text-sm text-muted-foreground">Loading...</div>}>
<WaveformPlayer src={originalBlobUrl!} />
</Suspense>
);
}
// document / data: placeholder card
const fname = selectedFileName ?? files[0]?.name ?? "file";
const fsize = selectedFileSize ?? files[0]?.size ?? 0;
const ext = fname.split(".").pop()?.toUpperCase() ?? "";
return (
<div className="flex items-center justify-center h-full">
<div className="text-center p-6 max-w-xs">
<div className="mx-auto w-14 h-14 rounded-2xl bg-muted flex items-center justify-center mb-3">
<FileImage className="h-7 w-7 text-muted-foreground" />
</div>
<p className="font-medium text-foreground mb-1">{fname}</p>
<p className="text-xs text-muted-foreground">
{ext} &middot; {formatFileSize(fsize)}
</p>
</div>
</div>
);
}
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Mobile Layout */ /* Mobile Layout */
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
@@ -383,7 +486,12 @@ export function AutomatePage() {
<div className="flex-1 overflow-y-auto px-4 py-3"> <div className="flex-1 overflow-y-auto px-4 py-3">
{!hasFile && ( {!hasFile && (
<div className="mb-4 space-y-2"> <div className="mb-4 space-y-2">
<Dropzone onFiles={handleFiles} accept="image/*" multiple currentFiles={files} /> <Dropzone
onFiles={handleFiles}
multiple
currentFiles={files}
fileFilter={() => true}
/>
<button <button
type="button" type="button"
onClick={() => setLibraryModalOpen(true)} onClick={() => setLibraryModalOpen(true)}
@@ -425,18 +533,11 @@ export function AutomatePage() {
</div> </div>
)} )}
{/* Mobile image preview / result */} {/* Mobile preview / result */}
{hasFile && hasProcessed && originalBlobUrl && ( {hasFile && hasProcessed && originalBlobUrl && (
<div className="mb-3 rounded-lg border border-border overflow-hidden"> <div className="mb-3 rounded-lg border border-border overflow-hidden">
<div className="relative h-48"> <div className="relative h-48">{renderPipelinePreview("result")}</div>
<BeforeAfterSlider {processedSize != null && currentEntry?.previewKind === "image" && (
beforeSrc={originalBlobUrl}
afterSrc={processedUrl as string}
beforeSize={originalSize ?? undefined}
afterSize={processedSize ?? undefined}
/>
</div>
{processedSize != null && (
<div className="flex items-center justify-between px-3 py-1.5 border-t border-border text-xs text-muted-foreground"> <div className="flex items-center justify-between px-3 py-1.5 border-t border-border text-xs text-muted-foreground">
<span className="truncate">{selectedFileName ?? files[0].name}</span> <span className="truncate">{selectedFileName ?? files[0].name}</span>
<span> <span>
@@ -449,11 +550,7 @@ export function AutomatePage() {
{hasFile && !hasProcessed && originalBlobUrl && currentEntry?.status !== "failed" && ( {hasFile && !hasProcessed && originalBlobUrl && currentEntry?.status !== "failed" && (
<div className="mb-3 rounded-lg border border-border overflow-hidden h-40 flex items-center justify-center bg-muted/20"> <div className="mb-3 rounded-lg border border-border overflow-hidden h-40 flex items-center justify-center bg-muted/20">
<ImageViewer {renderPipelinePreview("original")}
src={originalBlobUrl}
filename={selectedFileName ?? files[0].name}
fileSize={selectedFileSize ?? files[0].size}
/>
</div> </div>
)} )}
@@ -949,10 +1046,10 @@ export function AutomatePage() {
<div className="w-full max-h-[120px] overflow-hidden"> <div className="w-full max-h-[120px] overflow-hidden">
<Dropzone <Dropzone
onFiles={handleFiles} onFiles={handleFiles}
accept="image/*"
multiple multiple
currentFiles={files} currentFiles={files}
compact compact
fileFilter={() => true}
/> />
</div> </div>
<button <button
@@ -974,25 +1071,13 @@ export function AutomatePage() {
</div> </div>
)} )}
{hasFile && hasProcessed && originalBlobUrl && ( {hasFile && hasProcessed && originalBlobUrl && renderPipelinePreview("result")}
<BeforeAfterSlider
beforeSrc={originalBlobUrl}
afterSrc={processedUrl as string}
beforeSize={originalSize ?? undefined}
afterSize={processedSize ?? undefined}
/>
)}
{hasFile && {hasFile &&
!hasProcessed && !hasProcessed &&
originalBlobUrl && originalBlobUrl &&
currentEntry?.status !== "failed" && ( currentEntry?.status !== "failed" &&
<ImageViewer renderPipelinePreview("original")}
src={originalBlobUrl}
filename={selectedFileName ?? files[0].name}
fileSize={selectedFileSize ?? files[0].size}
/>
)}
</div> </div>
{hasMultiple && ( {hasMultiple && (
+12 -12
View File
@@ -2436,13 +2436,13 @@ export const ar: TranslationKeys = {
generatingPreview: "جاري إنشاء المعاينة...", generatingPreview: "جاري إنشاء المعاينة...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"لا يمكن معاينة ملفات {ext} في المتصفح. ستتم معالجة الملف بشكل طبيعي.", "لا يمكن معاينة ملفات {ext} في المتصفح. ستتم معالجة الملف بشكل طبيعي.",
previousImage: "الصورة السابقة", previousImage: "الملف السابق",
nextImage: "الصورة التالية", nextImage: "الملف التالي",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "الإعدادات", settingsLabel: "الإعدادات",
downloadAllZip: "تحميل الكل (ZIP)", downloadAllZip: "تحميل الكل (ZIP)",
hideSettings: "إخفاء الإعدادات", hideSettings: "إخفاء الإعدادات",
imageArea: "منطقة الصورة", imageArea: "منطقة المعاينة",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3176,10 +3176,10 @@ export const ar: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "إلغاء", cancelButton: "إلغاء",
preview: "معاينة", preview: "معاينة",
previousImage: "الصورة السابقة", previousImage: "الملف السابق",
nextImage: "الصورة التالية", nextImage: "الملف التالي",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "منطقة الصورة", imageArea: "منطقة المعاينة",
templateSocialMedia: "جاهز لوسائل التواصل", templateSocialMedia: "جاهز لوسائل التواصل",
templatePrivacyClean: "تنظيف الخصوصية", templatePrivacyClean: "تنظيف الخصوصية",
templateWebOptimization: "تحسين للويب", templateWebOptimization: "تحسين للويب",
@@ -3399,14 +3399,14 @@ export const ar: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "الملف السابق",
nextImage: "Next image", nextImage: "الملف التالي",
imageArea: "Image area", imageArea: "منطقة المعاينة",
imageViewer: "Image viewer", imageViewer: "عارض الملفات",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "منطقة إسقاط الملفات",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "عناصر تحكم المعاينة",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2450,13 +2450,13 @@ export const de: TranslationKeys = {
generatingPreview: "Vorschau wird generiert...", generatingPreview: "Vorschau wird generiert...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"{ext}-Dateien koennen im Browser nicht angezeigt werden. Das Werkzeug verarbeitet diese Datei trotzdem.", "{ext}-Dateien koennen im Browser nicht angezeigt werden. Das Werkzeug verarbeitet diese Datei trotzdem.",
previousImage: "Vorheriges Bild", previousImage: "Vorherige Datei",
nextImage: "Naechstes Bild", nextImage: "Naechste Datei",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Einstellungen", settingsLabel: "Einstellungen",
downloadAllZip: "Alle herunterladen (ZIP)", downloadAllZip: "Alle herunterladen (ZIP)",
hideSettings: "Einstellungen ausblenden", hideSettings: "Einstellungen ausblenden",
imageArea: "Bildbereich", imageArea: "Vorschaubereich",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3205,10 +3205,10 @@ export const de: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Abbrechen", cancelButton: "Abbrechen",
preview: "Vorschau", preview: "Vorschau",
previousImage: "Vorheriges Bild", previousImage: "Vorherige Datei",
nextImage: "Naechstes Bild", nextImage: "Naechste Datei",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Bildbereich", imageArea: "Vorschaubereich",
templateSocialMedia: "Social Media fertig", templateSocialMedia: "Social Media fertig",
templatePrivacyClean: "Datenschutzbereinigung", templatePrivacyClean: "Datenschutzbereinigung",
templateWebOptimization: "Weboptimierung", templateWebOptimization: "Weboptimierung",
@@ -3430,14 +3430,14 @@ export const de: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Vorherige Datei",
nextImage: "Next image", nextImage: "Naechste Datei",
imageArea: "Image area", imageArea: "Vorschaubereich",
imageViewer: "Image viewer", imageViewer: "Dateibetrachter",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Datei-Ablagezone",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Vorschausteuerung",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2400,13 +2400,13 @@ export const en = {
generatingPreview: "Generating preview...", generatingPreview: "Generating preview...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"{ext} files cannot be previewed in the browser. The tool will still process this file normally.", "{ext} files cannot be previewed in the browser. The tool will still process this file normally.",
previousImage: "Previous image", previousImage: "Previous file",
nextImage: "Next image", nextImage: "Next file",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Settings", settingsLabel: "Settings",
downloadAllZip: "Download All (ZIP)", downloadAllZip: "Download All (ZIP)",
hideSettings: "Hide Settings", hideSettings: "Hide Settings",
imageArea: "Image area", imageArea: "Preview area",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3142,10 +3142,10 @@ export const en = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Cancel", cancelButton: "Cancel",
preview: "Preview", preview: "Preview",
previousImage: "Previous image", previousImage: "Previous file",
nextImage: "Next image", nextImage: "Next file",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Image area", imageArea: "Preview area",
templateSocialMedia: "Social Media Ready", templateSocialMedia: "Social Media Ready",
templatePrivacyClean: "Privacy Clean", templatePrivacyClean: "Privacy Clean",
templateWebOptimization: "Web Optimization", templateWebOptimization: "Web Optimization",
@@ -3366,14 +3366,14 @@ export const en = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Previous file",
nextImage: "Next image", nextImage: "Next file",
imageArea: "Image area", imageArea: "Preview area",
imageViewer: "Image viewer", imageViewer: "File viewer",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "File drop zone",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Preview controls",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2432,13 +2432,13 @@ export const es: TranslationKeys = {
generatingPreview: "Generando vista previa...", generatingPreview: "Generando vista previa...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"Los archivos {ext} no se pueden previsualizar en el navegador. La herramienta procesara este archivo normalmente.", "Los archivos {ext} no se pueden previsualizar en el navegador. La herramienta procesara este archivo normalmente.",
previousImage: "Imagen anterior", previousImage: "Archivo anterior",
nextImage: "Siguiente imagen", nextImage: "Siguiente archivo",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Configuracion", settingsLabel: "Configuracion",
downloadAllZip: "Descargar todo (ZIP)", downloadAllZip: "Descargar todo (ZIP)",
hideSettings: "Ocultar configuracion", hideSettings: "Ocultar configuracion",
imageArea: "Area de imagen", imageArea: "Area de vista previa",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3182,10 +3182,10 @@ export const es: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Cancelar", cancelButton: "Cancelar",
preview: "Vista previa", preview: "Vista previa",
previousImage: "Imagen anterior", previousImage: "Archivo anterior",
nextImage: "Siguiente imagen", nextImage: "Siguiente archivo",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Area de imagen", imageArea: "Area de vista previa",
templateSocialMedia: "Listo para redes sociales", templateSocialMedia: "Listo para redes sociales",
templatePrivacyClean: "Limpieza de privacidad", templatePrivacyClean: "Limpieza de privacidad",
templateWebOptimization: "Optimizacion web", templateWebOptimization: "Optimizacion web",
@@ -3408,14 +3408,14 @@ export const es: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Archivo anterior",
nextImage: "Next image", nextImage: "Siguiente archivo",
imageArea: "Image area", imageArea: "Area de vista previa",
imageViewer: "Image viewer", imageViewer: "Visor de archivos",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Zona para soltar archivos",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Controles de vista previa",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2451,13 +2451,13 @@ export const fr: TranslationKeys = {
generatingPreview: "Generation de l'apercu...", generatingPreview: "Generation de l'apercu...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"Les fichiers {ext} ne peuvent pas etre previsualises dans le navigateur. L'outil traitera ce fichier normalement.", "Les fichiers {ext} ne peuvent pas etre previsualises dans le navigateur. L'outil traitera ce fichier normalement.",
previousImage: "Image precedente", previousImage: "Fichier precedent",
nextImage: "Image suivante", nextImage: "Fichier suivant",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Parametres", settingsLabel: "Parametres",
downloadAllZip: "Tout telecharger (ZIP)", downloadAllZip: "Tout telecharger (ZIP)",
hideSettings: "Masquer les parametres", hideSettings: "Masquer les parametres",
imageArea: "Zone d'image", imageArea: "Zone d'apercu",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3203,10 +3203,10 @@ export const fr: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Annuler", cancelButton: "Annuler",
preview: "Apercu", preview: "Apercu",
previousImage: "Image precedente", previousImage: "Fichier precedent",
nextImage: "Image suivante", nextImage: "Fichier suivant",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Zone d'image", imageArea: "Zone d'apercu",
templateSocialMedia: "Pret pour les reseaux sociaux", templateSocialMedia: "Pret pour les reseaux sociaux",
templatePrivacyClean: "Nettoyage de confidentialite", templatePrivacyClean: "Nettoyage de confidentialite",
templateWebOptimization: "Optimisation web", templateWebOptimization: "Optimisation web",
@@ -3429,14 +3429,14 @@ export const fr: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Fichier precedent",
nextImage: "Next image", nextImage: "Fichier suivant",
imageArea: "Image area", imageArea: "Zone d'apercu",
imageViewer: "Image viewer", imageViewer: "Visionneuse de fichiers",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Zone de depot de fichiers",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Controles d'apercu",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2433,13 +2433,13 @@ export const hi: TranslationKeys = {
generatingPreview: "प्रीव्यू जनरेट हो रहा है...", generatingPreview: "प्रीव्यू जनरेट हो रहा है...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"{ext} फाइलें ब्राउज़र में प्रीव्यू नहीं हो सकतीं। टूल इस फाइल को सामान्य रूप से प्रोसेस करेगा।", "{ext} फाइलें ब्राउज़र में प्रीव्यू नहीं हो सकतीं। टूल इस फाइल को सामान्य रूप से प्रोसेस करेगा।",
previousImage: "पिछली इमेज", previousImage: "पिछली फ़ाइल",
nextImage: "अगली इमेज", nextImage: "अगली फ़ाइल",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "सेटिंग्स", settingsLabel: "सेटिंग्स",
downloadAllZip: "सभी डाउनलोड करें (ZIP)", downloadAllZip: "सभी डाउनलोड करें (ZIP)",
hideSettings: "सेटिंग्स छुपाएं", hideSettings: "सेटिंग्स छुपाएं",
imageArea: "इमेज क्षेत्र", imageArea: "पूर्वावलोकन क्षेत्र",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3173,10 +3173,10 @@ export const hi: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "रद्द करें", cancelButton: "रद्द करें",
preview: "प्रीव्यू", preview: "प्रीव्यू",
previousImage: "पिछली इमेज", previousImage: "पिछली फ़ाइल",
nextImage: "अगली इमेज", nextImage: "अगली फ़ाइल",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "इमेज क्षेत्र", imageArea: "पूर्वावलोकन क्षेत्र",
templateSocialMedia: "सोशल मीडिया रेडी", templateSocialMedia: "सोशल मीडिया रेडी",
templatePrivacyClean: "प्राइवेसी क्लीन", templatePrivacyClean: "प्राइवेसी क्लीन",
templateWebOptimization: "वेब ऑप्टिमाइज़ेशन", templateWebOptimization: "वेब ऑप्टिमाइज़ेशन",
@@ -3396,14 +3396,14 @@ export const hi: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "पिछली फ़ाइल",
nextImage: "Next image", nextImage: "अगली फ़ाइल",
imageArea: "Image area", imageArea: "पूर्वावलोकन क्षेत्र",
imageViewer: "Image viewer", imageViewer: "फ़ाइल व्यूअर",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "फ़ाइल ड्रॉप ज़ोन",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "पूर्वावलोकन नियंत्रण",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2448,13 +2448,13 @@ export const pl: TranslationKeys = {
generatingPreview: "Generowanie podglądu...", generatingPreview: "Generowanie podglądu...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"Plików {ext} nie można podejrzeć w przeglądarce. Narzędzie nadal przetworzy ten plik.", "Plików {ext} nie można podejrzeć w przeglądarce. Narzędzie nadal przetworzy ten plik.",
previousImage: "Poprzedni obraz", previousImage: "Poprzedni plik",
nextImage: "Następny obraz", nextImage: "Następny plik",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Ustawienia", settingsLabel: "Ustawienia",
downloadAllZip: "Pobierz wszystko (ZIP)", downloadAllZip: "Pobierz wszystko (ZIP)",
hideSettings: "Ukryj ustawienia", hideSettings: "Ukryj ustawienia",
imageArea: "Obszar obrazu", imageArea: "Obszar podglądu",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3200,10 +3200,10 @@ export const pl: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Anuluj", cancelButton: "Anuluj",
preview: "Podgląd", preview: "Podgląd",
previousImage: "Poprzedni obraz", previousImage: "Poprzedni plik",
nextImage: "Następny obraz", nextImage: "Następny plik",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Obszar obrazu", imageArea: "Obszar podglądu",
templateSocialMedia: "Gotowe dla mediów społecznościowych", templateSocialMedia: "Gotowe dla mediów społecznościowych",
templatePrivacyClean: "Czyszczenie prywatności", templatePrivacyClean: "Czyszczenie prywatności",
templateWebOptimization: "Optymalizacja webowa", templateWebOptimization: "Optymalizacja webowa",
@@ -3425,14 +3425,14 @@ export const pl: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Poprzedni plik",
nextImage: "Next image", nextImage: "Następny plik",
imageArea: "Image area", imageArea: "Obszar podglądu",
imageViewer: "Image viewer", imageViewer: "Przeglądarka plików",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Strefa upuszczania plików",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Sterowanie podglądem",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2444,13 +2444,13 @@ export const ptBR: TranslationKeys = {
generatingPreview: "Gerando visualizacao...", generatingPreview: "Gerando visualizacao...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"Arquivos {ext} nao podem ser visualizados no navegador. A ferramenta processara este arquivo normalmente.", "Arquivos {ext} nao podem ser visualizados no navegador. A ferramenta processara este arquivo normalmente.",
previousImage: "Imagem anterior", previousImage: "Arquivo anterior",
nextImage: "Proxima imagem", nextImage: "Proximo arquivo",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Configuracoes", settingsLabel: "Configuracoes",
downloadAllZip: "Baixar tudo (ZIP)", downloadAllZip: "Baixar tudo (ZIP)",
hideSettings: "Ocultar configuracoes", hideSettings: "Ocultar configuracoes",
imageArea: "Area da imagem", imageArea: "Area de visualizacao",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3193,10 +3193,10 @@ export const ptBR: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Cancelar", cancelButton: "Cancelar",
preview: "Visualizacao", preview: "Visualizacao",
previousImage: "Imagem anterior", previousImage: "Arquivo anterior",
nextImage: "Proxima imagem", nextImage: "Proximo arquivo",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Area da imagem", imageArea: "Area de visualizacao",
templateSocialMedia: "Pronto para redes sociais", templateSocialMedia: "Pronto para redes sociais",
templatePrivacyClean: "Limpeza de privacidade", templatePrivacyClean: "Limpeza de privacidade",
templateWebOptimization: "Otimizacao para web", templateWebOptimization: "Otimizacao para web",
@@ -3418,14 +3418,14 @@ export const ptBR: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Arquivo anterior",
nextImage: "Next image", nextImage: "Proximo arquivo",
imageArea: "Image area", imageArea: "Area de visualizacao",
imageViewer: "Image viewer", imageViewer: "Visualizador de arquivos",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Zona de soltar arquivos",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Controles de visualizacao",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2446,13 +2446,13 @@ export const ru: TranslationKeys = {
generatingPreview: "Генерация предпросмотра...", generatingPreview: "Генерация предпросмотра...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"Файлы {ext} невозможно просмотреть в браузере. Инструмент всё равно обработает этот файл.", "Файлы {ext} невозможно просмотреть в браузере. Инструмент всё равно обработает этот файл.",
previousImage: "Предыдущее изображение", previousImage: "Предыдущий файл",
nextImage: "Следующее изображение", nextImage: "Следующий файл",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Настройки", settingsLabel: "Настройки",
downloadAllZip: "Скачать всё (ZIP)", downloadAllZip: "Скачать всё (ZIP)",
hideSettings: "Скрыть настройки", hideSettings: "Скрыть настройки",
imageArea: "Область изображения", imageArea: "Область предпросмотра",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3192,10 +3192,10 @@ export const ru: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Отмена", cancelButton: "Отмена",
preview: "Предпросмотр", preview: "Предпросмотр",
previousImage: "Предыдущее изображение", previousImage: "Предыдущий файл",
nextImage: "Следующее изображение", nextImage: "Следующий файл",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Область изображения", imageArea: "Область предпросмотра",
templateSocialMedia: "Готово для соцсетей", templateSocialMedia: "Готово для соцсетей",
templatePrivacyClean: "Очистка конфиденциальности", templatePrivacyClean: "Очистка конфиденциальности",
templateWebOptimization: "Веб-оптимизация", templateWebOptimization: "Веб-оптимизация",
@@ -3416,14 +3416,14 @@ export const ru: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Предыдущий файл",
nextImage: "Next image", nextImage: "Следующий файл",
imageArea: "Image area", imageArea: "Область предпросмотра",
imageViewer: "Image viewer", imageViewer: "Просмотр файла",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Зона перетаскивания файлов",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Управление предпросмотром",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2443,13 +2443,13 @@ export const sv: TranslationKeys = {
generatingPreview: "Genererar forhandsvisning...", generatingPreview: "Genererar forhandsvisning...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"{ext}-filer kan inte forhandsvisas i webblasaren. Verktyget bearbetar anda denna fil.", "{ext}-filer kan inte forhandsvisas i webblasaren. Verktyget bearbetar anda denna fil.",
previousImage: "Foregaende bild", previousImage: "Foregaende fil",
nextImage: "Nasta bild", nextImage: "Nasta fil",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Installningar", settingsLabel: "Installningar",
downloadAllZip: "Ladda ner alla (ZIP)", downloadAllZip: "Ladda ner alla (ZIP)",
hideSettings: "Dolj installningar", hideSettings: "Dolj installningar",
imageArea: "Bildomrade", imageArea: "Forhandsvisningsomrade",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3187,10 +3187,10 @@ export const sv: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Avbryt", cancelButton: "Avbryt",
preview: "Forhandsvisning", preview: "Forhandsvisning",
previousImage: "Foregaende bild", previousImage: "Foregaende fil",
nextImage: "Nasta bild", nextImage: "Nasta fil",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Bildomrade", imageArea: "Forhandsvisningsomrade",
templateSocialMedia: "Sociala medier-redo", templateSocialMedia: "Sociala medier-redo",
templatePrivacyClean: "Integritetsrensning", templatePrivacyClean: "Integritetsrensning",
templateWebOptimization: "Webboptimering", templateWebOptimization: "Webboptimering",
@@ -3411,14 +3411,14 @@ export const sv: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Foregaende fil",
nextImage: "Next image", nextImage: "Nasta fil",
imageArea: "Image area", imageArea: "Forhandsvisningsomrade",
imageViewer: "Image viewer", imageViewer: "Filvisare",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Filslappzon",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Forhandsvisningskontroller",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2425,13 +2425,13 @@ export const th: TranslationKeys = {
generatingPreview: "กำลังสร้างตัวอย่าง...", generatingPreview: "กำลังสร้างตัวอย่าง...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"ไม่สามารถแสดงตัวอย่างไฟล์ {ext} ในเบราว์เซอร์ได้ เครื่องมือจะยังประมวลผลไฟล์นี้ตามปกติ", "ไม่สามารถแสดงตัวอย่างไฟล์ {ext} ในเบราว์เซอร์ได้ เครื่องมือจะยังประมวลผลไฟล์นี้ตามปกติ",
previousImage: "ภาพก่อนหน้า", previousImage: "ไฟล์ก่อนหน้า",
nextImage: "ภาพถัดไป", nextImage: "ไฟล์ถัดไป",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "ตั้งค่า", settingsLabel: "ตั้งค่า",
downloadAllZip: "ดาวน์โหลดทั้งหมด (ZIP)", downloadAllZip: "ดาวน์โหลดทั้งหมด (ZIP)",
hideSettings: "ซ่อนการตั้งค่า", hideSettings: "ซ่อนการตั้งค่า",
imageArea: "พื้นที่ภาพ", imageArea: "พื้นที่แสดงตัวอย่าง",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3164,10 +3164,10 @@ export const th: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "ยกเลิก", cancelButton: "ยกเลิก",
preview: "ตัวอย่าง", preview: "ตัวอย่าง",
previousImage: "ภาพก่อนหน้า", previousImage: "ไฟล์ก่อนหน้า",
nextImage: "ภาพถัดไป", nextImage: "ไฟล์ถัดไป",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "พื้นที่ภาพ", imageArea: "พื้นที่แสดงตัวอย่าง",
templateSocialMedia: "พร้อมสำหรับสื่อสังคม", templateSocialMedia: "พร้อมสำหรับสื่อสังคม",
templatePrivacyClean: "ล้างข้อมูลความเป็นส่วนตัว", templatePrivacyClean: "ล้างข้อมูลความเป็นส่วนตัว",
templateWebOptimization: "เพิ่มประสิทธิภาพเว็บ", templateWebOptimization: "เพิ่มประสิทธิภาพเว็บ",
@@ -3387,14 +3387,14 @@ export const th: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "ไฟล์ก่อนหน้า",
nextImage: "Next image", nextImage: "ไฟล์ถัดไป",
imageArea: "Image area", imageArea: "พื้นที่แสดงตัวอย่าง",
imageViewer: "Image viewer", imageViewer: "ตัวแสดงไฟล์",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "โซนวางไฟล์",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "การควบคุมการแสดงตัวอย่าง",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2448,13 +2448,13 @@ export const tr: TranslationKeys = {
generatingPreview: "Önizleme oluşturuluyor...", generatingPreview: "Önizleme oluşturuluyor...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"{ext} dosyaları tarayıcıda önizlenemez. Araç bu dosyayı yine de normal olarak işleyecektir.", "{ext} dosyaları tarayıcıda önizlenemez. Araç bu dosyayı yine de normal olarak işleyecektir.",
previousImage: "Önceki görüntü", previousImage: "Önceki dosya",
nextImage: "Sonraki görüntü", nextImage: "Sonraki dosya",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Ayarlar", settingsLabel: "Ayarlar",
downloadAllZip: "Tümünü İndir (ZIP)", downloadAllZip: "Tümünü İndir (ZIP)",
hideSettings: "Ayarları Gizle", hideSettings: "Ayarları Gizle",
imageArea: "Görüntü alanı", imageArea: "Önizleme alanı",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3197,10 +3197,10 @@ export const tr: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "İptal", cancelButton: "İptal",
preview: "Önizleme", preview: "Önizleme",
previousImage: "Önceki görüntü", previousImage: "Önceki dosya",
nextImage: "Sonraki görüntü", nextImage: "Sonraki dosya",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Görüntü alanı", imageArea: "Önizleme alanı",
templateSocialMedia: "Sosyal Medyaya Hazır", templateSocialMedia: "Sosyal Medyaya Hazır",
templatePrivacyClean: "Gizlilik Temizliği", templatePrivacyClean: "Gizlilik Temizliği",
templateWebOptimization: "Web Optimizasyonu", templateWebOptimization: "Web Optimizasyonu",
@@ -3422,14 +3422,14 @@ export const tr: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Önceki dosya",
nextImage: "Next image", nextImage: "Sonraki dosya",
imageArea: "Image area", imageArea: "Önizleme alanı",
imageViewer: "Image viewer", imageViewer: "Dosya görüntüleyici",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Dosya bırakma alanı",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Önizleme kontrolleri",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2446,13 +2446,13 @@ export const uk: TranslationKeys = {
generatingPreview: "Генерація попереднього перегляду...", generatingPreview: "Генерація попереднього перегляду...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"Файли {ext} неможливо переглянути у браузері. Інструмент все одно обробить цей файл.", "Файли {ext} неможливо переглянути у браузері. Інструмент все одно обробить цей файл.",
previousImage: "Попереднє зображення", previousImage: "Попередній файл",
nextImage: "Наступне зображення", nextImage: "Наступний файл",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Налаштування", settingsLabel: "Налаштування",
downloadAllZip: "Завантажити все (ZIP)", downloadAllZip: "Завантажити все (ZIP)",
hideSettings: "Сховати налаштування", hideSettings: "Сховати налаштування",
imageArea: "Область зображення", imageArea: "Область попереднього перегляду",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3193,10 +3193,10 @@ export const uk: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Скасувати", cancelButton: "Скасувати",
preview: "Попередній перегляд", preview: "Попередній перегляд",
previousImage: "Попереднє зображення", previousImage: "Попередній файл",
nextImage: "Наступне зображення", nextImage: "Наступний файл",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Область зображення", imageArea: "Область попереднього перегляду",
templateSocialMedia: "Готово для соцмереж", templateSocialMedia: "Готово для соцмереж",
templatePrivacyClean: "Очищення конфіденційності", templatePrivacyClean: "Очищення конфіденційності",
templateWebOptimization: "Веб-оптимізація", templateWebOptimization: "Веб-оптимізація",
@@ -3417,14 +3417,14 @@ export const uk: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Попередній файл",
nextImage: "Next image", nextImage: "Наступний файл",
imageArea: "Image area", imageArea: "Область попереднього перегляду",
imageViewer: "Image viewer", imageViewer: "Переглядач файлів",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Зона перетягування файлів",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Елементи керування переглядом",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2445,13 +2445,13 @@ export const vi: TranslationKeys = {
generatingPreview: "Đang tạo bản xem trước...", generatingPreview: "Đang tạo bản xem trước...",
cannotPreviewWillProcess: cannotPreviewWillProcess:
"Không thể xem trước tệp {ext} trong trình duyệt. Công cụ vẫn sẽ xử lý tệp này bình thường.", "Không thể xem trước tệp {ext} trong trình duyệt. Công cụ vẫn sẽ xử lý tệp này bình thường.",
previousImage: "Ảnh trước", previousImage: "Tệp trước",
nextImage: "Ảnh tiếp theo", nextImage: "Tệp tiếp theo",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "Cài đặt", settingsLabel: "Cài đặt",
downloadAllZip: "Tải tất cả (ZIP)", downloadAllZip: "Tải tất cả (ZIP)",
hideSettings: "Ẩn cài đặt", hideSettings: "Ẩn cài đặt",
imageArea: "Vùng ảnh", imageArea: "Vùng xem trước",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3187,10 +3187,10 @@ export const vi: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "Hủy", cancelButton: "Hủy",
preview: "Xem trước", preview: "Xem trước",
previousImage: "Ảnh trước", previousImage: "Tệp trước",
nextImage: "Ảnh tiếp theo", nextImage: "Tệp tiếp theo",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "Vùng ảnh", imageArea: "Vùng xem trước",
templateSocialMedia: "Sẵn sàng cho mạng xã hội", templateSocialMedia: "Sẵn sàng cho mạng xã hội",
templatePrivacyClean: "Dọn dẹp quyền riêng tư", templatePrivacyClean: "Dọn dẹp quyền riêng tư",
templateWebOptimization: "Tối ưu cho Web", templateWebOptimization: "Tối ưu cho Web",
@@ -3411,14 +3411,14 @@ export const vi: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "Tệp trước",
nextImage: "Next image", nextImage: "Tệp tiếp theo",
imageArea: "Image area", imageArea: "Vùng xem trước",
imageViewer: "Image viewer", imageViewer: "Trình xem tệp",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "Vùng thả tệp",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "Điều khiển xem trước",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2378,13 +2378,13 @@ export const zhCN: TranslationKeys = {
cannotPreviewDownload: "{type} 文件无法在浏览器中预览。请使用下载按钮保存文件。", cannotPreviewDownload: "{type} 文件无法在浏览器中预览。请使用下载按钮保存文件。",
generatingPreview: "正在生成预览...", generatingPreview: "正在生成预览...",
cannotPreviewWillProcess: "{ext} 文件无法在浏览器中预览。该工具仍会正常处理此文件。", cannotPreviewWillProcess: "{ext} 文件无法在浏览器中预览。该工具仍会正常处理此文件。",
previousImage: "上一张图片", previousImage: "上一个文件",
nextImage: "下一张图片", nextImage: "下一个文件",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "设置", settingsLabel: "设置",
downloadAllZip: "全部下载(ZIP", downloadAllZip: "全部下载(ZIP",
hideSettings: "隐藏设置", hideSettings: "隐藏设置",
imageArea: "图片区域", imageArea: "预览区域",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3115,10 +3115,10 @@ export const zhCN: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "取消", cancelButton: "取消",
preview: "预览", preview: "预览",
previousImage: "上一张图片", previousImage: "上一个文件",
nextImage: "下一张图片", nextImage: "下一个文件",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "图片区域", imageArea: "预览区域",
templateSocialMedia: "社交媒体适配", templateSocialMedia: "社交媒体适配",
templatePrivacyClean: "隐私清理", templatePrivacyClean: "隐私清理",
templateWebOptimization: "网页优化", templateWebOptimization: "网页优化",
@@ -3337,14 +3337,14 @@ export const zhCN: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "上一个文件",
nextImage: "Next image", nextImage: "下一个文件",
imageArea: "Image area", imageArea: "预览区域",
imageViewer: "Image viewer", imageViewer: "文件查看器",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "文件拖放区域",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "预览控件",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+12 -12
View File
@@ -2376,13 +2376,13 @@ export const zhTW: TranslationKeys = {
cannotPreviewDownload: "{type}檔案無法在瀏覽器中預覽。請使用下載按鈕儲存檔案。", cannotPreviewDownload: "{type}檔案無法在瀏覽器中預覽。請使用下載按鈕儲存檔案。",
generatingPreview: "正在產生預覽...", generatingPreview: "正在產生預覽...",
cannotPreviewWillProcess: "{ext}檔案無法在瀏覽器中預覽。該工具仍會正常處理此檔案。", cannotPreviewWillProcess: "{ext}檔案無法在瀏覽器中預覽。該工具仍會正常處理此檔案。",
previousImage: "上一張影像", previousImage: "上一個檔案",
nextImage: "下一張影像", nextImage: "下一個檔案",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
settingsLabel: "設定", settingsLabel: "設定",
downloadAllZip: "全部下載(ZIP", downloadAllZip: "全部下載(ZIP",
hideSettings: "隱藏設定", hideSettings: "隱藏設定",
imageArea: "影像區域", imageArea: "預覽區域",
disabledByAdmin: "This tool has been disabled by your administrator", disabledByAdmin: "This tool has been disabled by your administrator",
browseOtherTools: "Browse other tools", browseOtherTools: "Browse other tools",
privacyNote: "Files are processed on your server and never leave your network", privacyNote: "Files are processed on your server and never leave your network",
@@ -3113,10 +3113,10 @@ export const zhTW: TranslationKeys = {
savingIndicator: "...", savingIndicator: "...",
cancelButton: "取消", cancelButton: "取消",
preview: "預覽", preview: "預覽",
previousImage: "上一張影像", previousImage: "上一個檔案",
nextImage: "下一張影像", nextImage: "下一個檔案",
imageCounter: "{current} / {total}", imageCounter: "{current} / {total}",
imageArea: "影像區域", imageArea: "預覽區域",
templateSocialMedia: "社群媒體就緒", templateSocialMedia: "社群媒體就緒",
templatePrivacyClean: "隱私清理", templatePrivacyClean: "隱私清理",
templateWebOptimization: "網頁最佳化", templateWebOptimization: "網頁最佳化",
@@ -3336,14 +3336,14 @@ export const zhTW: TranslationKeys = {
zoomOut: "Zoom out", zoomOut: "Zoom out",
fitToView: "Fit to view", fitToView: "Fit to view",
actualSize: "Actual size", actualSize: "Actual size",
previousImage: "Previous image", previousImage: "上一個檔案",
nextImage: "Next image", nextImage: "下一個檔案",
imageArea: "Image area", imageArea: "預覽區域",
imageViewer: "Image viewer", imageViewer: "檔案檢視器",
fileDropZone: "File drop zone", fileDropZone: "File drop zone",
imageDropZone: "Image drop zone", imageDropZone: "檔案拖放區域",
beforeAfterSlider: "Before/after comparison slider", beforeAfterSlider: "Before/after comparison slider",
imageControls: "Image controls", imageControls: "預覽控制項",
zoomControls: "Zoom controls", zoomControls: "Zoom controls",
dragToReorder: "Drag to reorder", dragToReorder: "Drag to reorder",
whiteBackground: "White background", whiteBackground: "White background",
+17 -1
View File
@@ -69,7 +69,7 @@ describe("sanitizeFilename", () => {
}); });
it("handles filename with only unknown extensions", () => { it("handles filename with only unknown extensions", () => {
expect(sanitizeFilename("data.csv")).toBe("data.csv"); expect(sanitizeFilename("data.xyz")).toBe("data.xyz");
}); });
it("truncates very long filenames over 200 bytes", () => { it("truncates very long filenames over 200 bytes", () => {
@@ -139,4 +139,20 @@ describe("sanitizeFilename", () => {
expect(result).toBe(`file.${ext}`); expect(result).toBe(`file.${ext}`);
} }
}); });
it("recognizes safe extensions across all modalities", () => {
const cases: Record<string, string> = {
"clip.mp4.exe": "clip.mp4",
"song.mp3.php": "song.mp3",
"report.csv.php": "report.csv",
"doc.docx.exe": "doc.docx",
"data.json.sh": "data.json",
"book.epub.php": "book.epub",
"sheet.xlsx.exe": "sheet.xlsx",
"subs.srt.php": "subs.srt",
};
for (const [input, expected] of Object.entries(cases)) {
expect(sanitizeFilename(input)).toBe(expected);
}
});
}); });