-
File Details
+
+ {t.files.fileDetailsHeading}
+
-
+
-
+
-
+
0 ? details.toolChain.map(toolName).join(", ") : "None"
+ details.toolChain.length > 0
+ ? details.toolChain.map(toolName).join(", ")
+ : t.files.none
}
/>
diff --git a/apps/web/src/components/files/file-list.tsx b/apps/web/src/components/files/file-list.tsx
index 8169f7db..a9e2c6a4 100644
--- a/apps/web/src/components/files/file-list.tsx
+++ b/apps/web/src/components/files/file-list.tsx
@@ -1,11 +1,14 @@
import { Download, Search, Trash2, Workflow } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { useNavigate } from "react-router-dom";
+import { useTranslation } from "@/contexts/i18n-context";
import { getFileDownloadUrl } from "@/lib/api";
+import { format } from "@/lib/format";
import { useFilesPageStore } from "@/stores/files-page-store";
import { FileListItem } from "./file-list-item";
export function FileList() {
+ const { t } = useTranslation();
const {
files,
checkedIds,
@@ -61,7 +64,7 @@ export function FileList() {
- {someChecked ? `${checkedIds.size} selected` : `${files.length} files`}
+ {someChecked
+ ? format(t.files.selectedCount, { count: checkedIds.size })
+ : format(t.files.fileCount, { count: files.length })}
{someChecked && (
<>
@@ -88,7 +93,7 @@ export function FileList() {
className="flex items-center gap-1 px-2 py-1 text-xs text-destructive hover:bg-destructive/10 rounded-lg transition-colors"
>
- Delete
+ {t.files.delete}
>
)}
@@ -124,7 +129,7 @@ export function FileList() {
)}
{!loading && !error && files.length === 0 && (
-
No files found
+
{t.files.noFilesFound}
)}
{!loading && !error && files.map((file) =>
)}
diff --git a/apps/web/src/components/files/files-nav.tsx b/apps/web/src/components/files/files-nav.tsx
index d092185f..3b47b4bf 100644
--- a/apps/web/src/components/files/files-nav.tsx
+++ b/apps/web/src/components/files/files-nav.tsx
@@ -1,17 +1,19 @@
import { Clock, Upload } from "lucide-react";
+import { useTranslation } from "@/contexts/i18n-context";
import { cn } from "@/lib/utils";
import { useFilesPageStore } from "@/stores/files-page-store";
export function FilesNav() {
+ const { t } = useTranslation();
const { activeTab, setActiveTab } = useFilesPageStore();
const items = [
- { id: "recent" as const, label: "Recent", icon: Clock },
- { id: "upload" as const, label: "Upload Files", icon: Upload },
+ { id: "recent" as const, label: t.files.recentTab, icon: Clock },
+ { id: "upload" as const, label: t.files.uploadTab, icon: Upload },
];
return (
-
-
My Files
+
+
{t.files.myFiles}
{items.map((item) => (
);
}
diff --git a/apps/web/src/components/tools/tool-palette.tsx b/apps/web/src/components/tools/tool-palette.tsx
index c8b8b156..418b4d27 100644
--- a/apps/web/src/components/tools/tool-palette.tsx
+++ b/apps/web/src/components/tools/tool-palette.tsx
@@ -66,12 +66,16 @@ export function ToolPalette({ onAddStep, className }: ToolPaletteProps) {
return (
-
+
{availableTools.length === 0 ? (
-
No tools found
+
{t.common.noToolsFound}
) : isSearching ? (
{availableTools.map((tool) => (
diff --git a/apps/web/src/hooks/use-editor-shortcuts.ts b/apps/web/src/hooks/use-editor-shortcuts.ts
index 223e249c..54911d49 100644
--- a/apps/web/src/hooks/use-editor-shortcuts.ts
+++ b/apps/web/src/hooks/use-editor-shortcuts.ts
@@ -394,6 +394,7 @@ export function useEditorShortcuts(callbacks?: {
useHotkeys(
"mod+a",
(e) => {
+ if (isInputFocused()) return;
e.preventDefault();
const state = useEditorStore.getState();
const allIds = state.objects.map((o) => o.id);
@@ -406,6 +407,7 @@ export function useEditorShortcuts(callbacks?: {
useHotkeys(
"mod+d",
(e) => {
+ if (isInputFocused()) return;
e.preventDefault();
useEditorStore.getState().setSelectedObjects([]);
useEditorStore.getState().setSelection(null);
@@ -496,6 +498,7 @@ export function useEditorShortcuts(callbacks?: {
useHotkeys(
"mod+t",
(e) => {
+ if (isInputFocused()) return;
e.preventDefault();
useEditorStore.getState().setTool("transform");
},
@@ -506,6 +509,7 @@ export function useEditorShortcuts(callbacks?: {
useHotkeys(
"mod+j",
(e) => {
+ if (isInputFocused()) return;
e.preventDefault();
const state = useEditorStore.getState();
state.duplicateLayer(state.activeLayerId);
@@ -517,6 +521,7 @@ export function useEditorShortcuts(callbacks?: {
useHotkeys(
"mod+shift+n",
(e) => {
+ if (isInputFocused()) return;
e.preventDefault();
useEditorStore.getState().addLayer();
},
diff --git a/apps/web/src/pages/not-found-page.tsx b/apps/web/src/pages/not-found-page.tsx
new file mode 100644
index 00000000..93a08c2e
--- /dev/null
+++ b/apps/web/src/pages/not-found-page.tsx
@@ -0,0 +1,22 @@
+import { Link } from "react-router-dom";
+import { useTranslation } from "@/contexts/i18n-context";
+
+export function NotFoundPage() {
+ const { t } = useTranslation();
+
+ return (
+
+
+
404
+
{t.common.pageNotFound}
+
{t.common.pageNotFoundDescription}
+
+ {t.common.goHome}
+
+
+
+ );
+}
diff --git a/apps/web/src/stores/editor-store.ts b/apps/web/src/stores/editor-store.ts
index 218ae24a..a4c41786 100644
--- a/apps/web/src/stores/editor-store.ts
+++ b/apps/web/src/stores/editor-store.ts
@@ -8,6 +8,7 @@ import type {
CanvasObject,
EditorLayer,
EditorState,
+ EraserMode,
FilterConfig,
SelectionMode,
StrokeDashStyle,
@@ -152,6 +153,8 @@ export const useEditorStore = create
()(
brushSize: 10,
brushOpacity: 1,
brushHardness: 1,
+ brushFlow: 1,
+ eraserMode: "brush" as EraserMode,
// --- Colors ---
foregroundColor: "#000000",
@@ -171,6 +174,7 @@ export const useEditorStore = create()(
selectionMode: "new" as SelectionMode,
magicWandTolerance: 32,
magicWandContiguous: true,
+ selectionFeather: 0,
// --- Crop ---
cropState: null,
@@ -954,6 +958,7 @@ export const useEditorStore = create()(
setSelectionMode: (mode) => set({ selectionMode: mode }),
setMagicWandTolerance: (v) => set({ magicWandTolerance: v }),
setMagicWandContiguous: (v: boolean) => set({ magicWandContiguous: v }),
+ setSelectionFeather: (v) => set({ selectionFeather: v }),
invertSelection: () => {
const { selection, canvasSize } = get();
@@ -1067,6 +1072,8 @@ export const useEditorStore = create()(
setBrushSize: (size) => set({ brushSize: Math.max(1, Math.min(MAX_BRUSH_SIZE, size)) }),
setBrushOpacity: (opacity) => set({ brushOpacity: Math.max(0, Math.min(1, opacity)) }),
setBrushHardness: (hardness) => set({ brushHardness: Math.max(0, Math.min(1, hardness)) }),
+ setBrushFlow: (flow) => set({ brushFlow: Math.max(0, Math.min(1, flow)) }),
+ setEraserMode: (mode) => set({ eraserMode: mode }),
// Clipboard
copyObjects: () => {
diff --git a/apps/web/src/stores/pipeline-store.ts b/apps/web/src/stores/pipeline-store.ts
index 7f9879e8..df1c619a 100644
--- a/apps/web/src/stores/pipeline-store.ts
+++ b/apps/web/src/stores/pipeline-store.ts
@@ -1,4 +1,5 @@
import { create } from "zustand";
+import { createJSONStorage, persist } from "zustand/middleware";
import { generateId } from "@/lib/utils";
export interface PipelineStep {
@@ -30,51 +31,63 @@ interface PipelineState {
reset: () => void;
}
-export const usePipelineStore = create((set, get) => ({
- steps: [],
- expandedStepId: null,
- savedPipelines: [],
+export const usePipelineStore = create()(
+ persist(
+ (set, get) => ({
+ steps: [],
+ expandedStepId: null,
+ savedPipelines: [],
- addStep: (toolId) => {
- const step: PipelineStep = { id: generateId(), toolId, settings: {} };
- set({ steps: [...get().steps, step], expandedStepId: step.id });
- },
+ addStep: (toolId) => {
+ const step: PipelineStep = { id: generateId(), toolId, settings: {} };
+ set({ steps: [...get().steps, step], expandedStepId: step.id });
+ },
- removeStep: (id) => {
- const { steps, expandedStepId } = get();
- set({
- steps: steps.filter((s) => s.id !== id),
- expandedStepId: expandedStepId === id ? null : expandedStepId,
- });
- },
+ removeStep: (id) => {
+ const { steps, expandedStepId } = get();
+ set({
+ steps: steps.filter((s) => s.id !== id),
+ expandedStepId: expandedStepId === id ? null : expandedStepId,
+ });
+ },
- reorderSteps: (activeId, overId) => {
- const { steps } = get();
- const oldIndex = steps.findIndex((s) => s.id === activeId);
- const newIndex = steps.findIndex((s) => s.id === overId);
- if (oldIndex < 0 || newIndex < 0) return;
- const reordered = [...steps];
- const [moved] = reordered.splice(oldIndex, 1);
- reordered.splice(newIndex, 0, moved);
- set({ steps: reordered });
- },
+ reorderSteps: (activeId, overId) => {
+ const { steps } = get();
+ const oldIndex = steps.findIndex((s) => s.id === activeId);
+ const newIndex = steps.findIndex((s) => s.id === overId);
+ if (oldIndex < 0 || newIndex < 0) return;
+ const reordered = [...steps];
+ const [moved] = reordered.splice(oldIndex, 1);
+ reordered.splice(newIndex, 0, moved);
+ set({ steps: reordered });
+ },
- updateStepSettings: (id, settings) => {
- set({ steps: get().steps.map((s) => (s.id === id ? { ...s, settings } : s)) });
- },
+ updateStepSettings: (id, settings) => {
+ set({ steps: get().steps.map((s) => (s.id === id ? { ...s, settings } : s)) });
+ },
- setExpandedStep: (id) => set({ expandedStepId: id }),
+ setExpandedStep: (id) => set({ expandedStepId: id }),
- loadSteps: (rawSteps) => {
- const steps = rawSteps.map((s) => ({
- id: generateId(),
- toolId: s.toolId,
- settings: { ...s.settings },
- }));
- set({ steps, expandedStepId: null });
- },
+ loadSteps: (rawSteps) => {
+ const steps = rawSteps.map((s) => ({
+ id: generateId(),
+ toolId: s.toolId,
+ settings: { ...s.settings },
+ }));
+ set({ steps, expandedStepId: null });
+ },
- setSavedPipelines: (pipelines) => set({ savedPipelines: pipelines }),
+ setSavedPipelines: (pipelines) => set({ savedPipelines: pipelines }),
- reset: () => set({ steps: [], expandedStepId: null, savedPipelines: [] }),
-}));
+ reset: () => set({ steps: [], expandedStepId: null, savedPipelines: [] }),
+ }),
+ {
+ name: "snapotter-pipeline",
+ storage: createJSONStorage(() => sessionStorage),
+ partialize: (state) => ({
+ steps: state.steps,
+ expandedStepId: state.expandedStepId,
+ }),
+ },
+ ),
+);
diff --git a/apps/web/src/types/editor.ts b/apps/web/src/types/editor.ts
index 34d631c4..43240e1f 100644
--- a/apps/web/src/types/editor.ts
+++ b/apps/web/src/types/editor.ts
@@ -1,5 +1,7 @@
// apps/web/src/types/editor.ts
+export type EraserMode = "brush" | "block";
+
export type SelectionMode = "new" | "add" | "subtract";
export type StrokeDashStyle = "solid" | "dashed" | "dotted";
@@ -281,6 +283,8 @@ export interface EditorState {
brushSize: number;
brushOpacity: number;
brushHardness: number;
+ brushFlow: number;
+ eraserMode: EraserMode;
// Colors
foregroundColor: string;
@@ -300,6 +304,7 @@ export interface EditorState {
selectionMode: SelectionMode;
magicWandTolerance: number;
magicWandContiguous: boolean;
+ selectionFeather: number;
// Crop
cropState: CropState | null;
@@ -425,6 +430,7 @@ export interface EditorState {
setSelectionMode: (mode: SelectionMode) => void;
setMagicWandTolerance: (v: number) => void;
setMagicWandContiguous: (v: boolean) => void;
+ setSelectionFeather: (v: number) => void;
invertSelection: () => void;
// Crop
@@ -435,6 +441,8 @@ export interface EditorState {
setBrushSize: (size: number) => void;
setBrushOpacity: (opacity: number) => void;
setBrushHardness: (hardness: number) => void;
+ setBrushFlow: (flow: number) => void;
+ setEraserMode: (mode: EraserMode) => void;
// Clipboard
copyObjects: () => void;
diff --git a/packages/shared/src/i18n/ar.ts b/packages/shared/src/i18n/ar.ts
index 4fba10d0..e0a2c2aa 100644
--- a/packages/shared/src/i18n/ar.ts
+++ b/packages/shared/src/i18n/ar.ts
@@ -36,7 +36,9 @@ export const ar: TranslationKeys = {
unexpectedError: "حدث خطأ غير متوقع.",
retry: "إعادة المحاولة",
privacyPolicy: "سياسة الخصوصية",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "الأساسيات",
optimization: "التحسين",
@@ -1686,6 +1688,9 @@ export const ar: TranslationKeys = {
githubLink: "مستودع GitHub",
docsLink: "التوثيق",
apiRefLink: "مرجع API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1826,7 +1831,9 @@ export const ar: TranslationKeys = {
pipelineName: "اسم Pipeline",
pipelineDescription: "الوصف (اختياري)",
noStepsPrompt: "أضف خطوات لبناء أتمتتك",
- step: "خطوة",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "خطوة",
},
nav: {
tools: "الأدوات",
@@ -1839,10 +1846,29 @@ export const ar: TranslationKeys = {
grid: "شبكة",
},
files: {
- recentTab: "الأخيرة",
- uploadTab: "رفع",
- fileDetailsAriaLabel: "تفاصيل الملف",
- fileDetailsHeading: "تفاصيل الملف",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "نوع الملف هذا غير مدعوم بهذه الأداة",
diff --git a/packages/shared/src/i18n/de.ts b/packages/shared/src/i18n/de.ts
index 6031e2f9..9631cc94 100644
--- a/packages/shared/src/i18n/de.ts
+++ b/packages/shared/src/i18n/de.ts
@@ -36,7 +36,9 @@ export const de: TranslationKeys = {
unexpectedError: "Ein unerwarteter Fehler ist aufgetreten.",
retry: "Erneut versuchen",
privacyPolicy: "Datenschutzerklaerung",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Grundlagen",
optimization: "Optimierung",
@@ -1711,6 +1713,9 @@ export const de: TranslationKeys = {
githubLink: "GitHub-Repository",
docsLink: "Dokumentation",
apiRefLink: "API-Referenz (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1855,7 +1860,9 @@ export const de: TranslationKeys = {
pipelineName: "Pipeline-Name",
pipelineDescription: "Beschreibung (optional)",
noStepsPrompt: "Fuegen Sie Schritte hinzu, um Ihre Automatisierung zu erstellen",
- step: "Schritt",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Schritt",
},
nav: {
tools: "Werkzeuge",
@@ -1868,10 +1875,29 @@ export const de: TranslationKeys = {
grid: "Raster",
},
files: {
- recentTab: "Zuletzt",
- uploadTab: "Hochladen",
- fileDetailsAriaLabel: "Dateidetails",
- fileDetailsHeading: "Dateidetails",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Dieser Dateityp wird von diesem Werkzeug nicht unterstuetzt",
diff --git a/packages/shared/src/i18n/en.ts b/packages/shared/src/i18n/en.ts
index a8351789..5e8f6c20 100644
--- a/packages/shared/src/i18n/en.ts
+++ b/packages/shared/src/i18n/en.ts
@@ -34,6 +34,8 @@ export const en = {
somethingWentWrong: "Something went wrong",
unexpectedError: "An unexpected error occurred.",
privacyPolicy: "Privacy Policy",
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
},
categories: {
essentials: "Essentials",
@@ -1431,7 +1433,8 @@ export const en = {
newPasswordPlaceholder: "New Password",
confirmPasswordPlaceholder: "Confirm New Password",
passwordsMismatch: "Passwords do not match",
- passwordTooShort: "Password must be at least 4 characters",
+ passwordTooShort:
+ "Password must be at least 8 characters with uppercase, lowercase, and a number",
changeSuccess: "Password changed successfully",
changeFailed: "Failed to change password",
currentPasswordIncorrect: "Current password is incorrect",
@@ -1645,6 +1648,9 @@ export const en = {
githubLink: "GitHub Repository",
docsLink: "Documentation",
apiRefLink: "API Reference (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1786,6 +1792,8 @@ export const en = {
pipelineName: "Pipeline Name",
pipelineDescription: "Description (optional)",
noStepsPrompt: "Add steps to build your automation",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
step: "Step",
},
nav: {
@@ -1799,10 +1807,29 @@ export const en = {
grid: "Grid",
},
files: {
+ myFiles: "My Files",
recentTab: "Recent",
- uploadTab: "Upload",
+ uploadTab: "Upload Files",
fileDetailsAriaLabel: "File Details",
fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "This file type is not supported by this tool",
diff --git a/packages/shared/src/i18n/es.ts b/packages/shared/src/i18n/es.ts
index cecfe36e..f1511ff0 100644
--- a/packages/shared/src/i18n/es.ts
+++ b/packages/shared/src/i18n/es.ts
@@ -36,7 +36,9 @@ export const es: TranslationKeys = {
unexpectedError: "Ocurrio un error inesperado.",
retry: "Reintentar",
privacyPolicy: "Politica de privacidad",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Esenciales",
optimization: "Optimizacion",
@@ -1690,6 +1692,9 @@ export const es: TranslationKeys = {
githubLink: "Repositorio en GitHub",
docsLink: "Documentacion",
apiRefLink: "Referencia API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1832,7 +1837,9 @@ export const es: TranslationKeys = {
pipelineName: "Nombre del Pipeline",
pipelineDescription: "Descripcion (opcional)",
noStepsPrompt: "Agrega pasos para construir tu automatizacion",
- step: "Paso",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Paso",
},
nav: {
tools: "Herramientas",
@@ -1845,10 +1852,29 @@ export const es: TranslationKeys = {
grid: "Cuadricula",
},
files: {
- recentTab: "Recientes",
- uploadTab: "Subir",
- fileDetailsAriaLabel: "Detalles del archivo",
- fileDetailsHeading: "Detalles del archivo",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Este tipo de archivo no es compatible con esta herramienta",
diff --git a/packages/shared/src/i18n/fr.ts b/packages/shared/src/i18n/fr.ts
index 8de25c01..cbf0c72e 100644
--- a/packages/shared/src/i18n/fr.ts
+++ b/packages/shared/src/i18n/fr.ts
@@ -36,7 +36,9 @@ export const fr: TranslationKeys = {
unexpectedError: "Une erreur inattendue est survenue.",
retry: "Réessayer",
privacyPolicy: "Politique de confidentialite",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Essentiels",
optimization: "Optimisation",
@@ -1709,6 +1711,9 @@ export const fr: TranslationKeys = {
githubLink: "Depot GitHub",
docsLink: "Documentation",
apiRefLink: "Reference API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1853,7 +1858,9 @@ export const fr: TranslationKeys = {
pipelineName: "Nom du Pipeline",
pipelineDescription: "Description (optionnel)",
noStepsPrompt: "Ajoutez des etapes pour construire votre automatisation",
- step: "Etape",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Etape",
},
nav: {
tools: "Outils",
@@ -1866,10 +1873,29 @@ export const fr: TranslationKeys = {
grid: "Grille",
},
files: {
- recentTab: "Recents",
- uploadTab: "Importer",
- fileDetailsAriaLabel: "Details du fichier",
- fileDetailsHeading: "Details du fichier",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Ce type de fichier n'est pas pris en charge par cet outil",
diff --git a/packages/shared/src/i18n/hi.ts b/packages/shared/src/i18n/hi.ts
index 6491561a..ec590886 100644
--- a/packages/shared/src/i18n/hi.ts
+++ b/packages/shared/src/i18n/hi.ts
@@ -36,7 +36,9 @@ export const hi: TranslationKeys = {
unexpectedError: "एक अप्रत्याशित त्रुटि हुई।",
retry: "पुनः प्रयास करें",
privacyPolicy: "गोपनीयता नीति",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "आवश्यक टूल्स",
optimization: "ऑप्टिमाइज़ेशन",
@@ -1682,6 +1684,9 @@ export const hi: TranslationKeys = {
githubLink: "GitHub रिपॉज़िटरी",
docsLink: "डॉक्यूमेंटेशन",
apiRefLink: "API रेफरेंस (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1823,7 +1828,9 @@ export const hi: TranslationKeys = {
pipelineName: "Pipeline का नाम",
pipelineDescription: "विवरण (वैकल्पिक)",
noStepsPrompt: "अपना ऑटोमेशन बनाने के लिए स्टेप्स जोड़ें",
- step: "स्टेप",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "स्टेप",
},
nav: {
tools: "टूल्स",
@@ -1836,10 +1843,29 @@ export const hi: TranslationKeys = {
grid: "ग्रिड",
},
files: {
- recentTab: "हाल के",
- uploadTab: "अपलोड",
- fileDetailsAriaLabel: "फाइल विवरण",
- fileDetailsHeading: "फाइल विवरण",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "इस फाइल प्रकार को यह टूल सपोर्ट नहीं करता",
diff --git a/packages/shared/src/i18n/id.ts b/packages/shared/src/i18n/id.ts
index bb506a19..bf9585b0 100644
--- a/packages/shared/src/i18n/id.ts
+++ b/packages/shared/src/i18n/id.ts
@@ -36,7 +36,9 @@ export const id: TranslationKeys = {
unexpectedError: "Terjadi kesalahan yang tidak terduga.",
retry: "Coba lagi",
privacyPolicy: "Kebijakan Privasi",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Dasar",
optimization: "Optimasi",
@@ -1698,6 +1700,9 @@ export const id: TranslationKeys = {
githubLink: "Repositori GitHub",
docsLink: "Dokumentasi",
apiRefLink: "Referensi API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1840,7 +1845,9 @@ export const id: TranslationKeys = {
pipelineName: "Nama Pipeline",
pipelineDescription: "Deskripsi (opsional)",
noStepsPrompt: "Tambahkan langkah untuk membangun otomasi Anda",
- step: "Langkah",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Langkah",
},
nav: {
tools: "Alat",
@@ -1853,10 +1860,29 @@ export const id: TranslationKeys = {
grid: "Grid",
},
files: {
- recentTab: "Terbaru",
- uploadTab: "Unggah",
- fileDetailsAriaLabel: "Detail File",
- fileDetailsHeading: "Detail File",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Jenis file ini tidak didukung oleh alat ini",
diff --git a/packages/shared/src/i18n/it.ts b/packages/shared/src/i18n/it.ts
index b7852550..4f4c4a85 100644
--- a/packages/shared/src/i18n/it.ts
+++ b/packages/shared/src/i18n/it.ts
@@ -36,7 +36,9 @@ export const it: TranslationKeys = {
unexpectedError: "Si e verificato un errore imprevisto.",
retry: "Riprova",
privacyPolicy: "Informativa sulla privacy",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Essenziali",
optimization: "Ottimizzazione",
@@ -1704,6 +1706,9 @@ export const it: TranslationKeys = {
githubLink: "Repository GitHub",
docsLink: "Documentazione",
apiRefLink: "Riferimento API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1847,7 +1852,9 @@ export const it: TranslationKeys = {
pipelineName: "Nome del Pipeline",
pipelineDescription: "Descrizione (opzionale)",
noStepsPrompt: "Aggiungi passaggi per costruire la tua automazione",
- step: "Passaggio",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Passaggio",
},
nav: {
tools: "Strumenti",
@@ -1860,10 +1867,29 @@ export const it: TranslationKeys = {
grid: "Griglia",
},
files: {
- recentTab: "Recenti",
- uploadTab: "Carica",
- fileDetailsAriaLabel: "Dettagli file",
- fileDetailsHeading: "Dettagli file",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Questo tipo di file non e supportato da questo strumento",
diff --git a/packages/shared/src/i18n/ja.ts b/packages/shared/src/i18n/ja.ts
index 7f1af33b..4f6f51e7 100644
--- a/packages/shared/src/i18n/ja.ts
+++ b/packages/shared/src/i18n/ja.ts
@@ -36,7 +36,9 @@ export const ja: TranslationKeys = {
unexpectedError: "予期しないエラーが発生しました。",
retry: "再試行",
privacyPolicy: "プライバシーポリシー",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "基本ツール",
optimization: "最適化",
@@ -1655,6 +1657,9 @@ export const ja: TranslationKeys = {
githubLink: "GitHubリポジトリ",
docsLink: "ドキュメント",
apiRefLink: "APIリファレンス(Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1796,7 +1801,9 @@ export const ja: TranslationKeys = {
pipelineName: "Pipeline名",
pipelineDescription: "説明(任意)",
noStepsPrompt: "ステップを追加して自動化を構築",
- step: "ステップ",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "ステップ",
},
nav: {
tools: "ツール",
@@ -1809,10 +1816,29 @@ export const ja: TranslationKeys = {
grid: "グリッド",
},
files: {
- recentTab: "最近",
- uploadTab: "アップロード",
- fileDetailsAriaLabel: "ファイル詳細",
- fileDetailsHeading: "ファイル詳細",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "このツールではサポートされていないファイルタイプです",
diff --git a/packages/shared/src/i18n/ko.ts b/packages/shared/src/i18n/ko.ts
index f814640f..1a910826 100644
--- a/packages/shared/src/i18n/ko.ts
+++ b/packages/shared/src/i18n/ko.ts
@@ -36,7 +36,9 @@ export const ko: TranslationKeys = {
unexpectedError: "예기치 않은 오류가 발생했습니다.",
retry: "재시도",
privacyPolicy: "개인정보 처리방침",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "기본 도구",
optimization: "최적화",
@@ -1640,6 +1642,9 @@ export const ko: TranslationKeys = {
githubLink: "GitHub 저장소",
docsLink: "문서",
apiRefLink: "API 레퍼런스 (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1781,7 +1786,9 @@ export const ko: TranslationKeys = {
pipelineName: "Pipeline 이름",
pipelineDescription: "설명 (선택)",
noStepsPrompt: "단계를 추가하여 자동화를 구성하세요",
- step: "단계",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "단계",
},
nav: {
tools: "도구",
@@ -1794,10 +1801,29 @@ export const ko: TranslationKeys = {
grid: "그리드",
},
files: {
- recentTab: "최근",
- uploadTab: "업로드",
- fileDetailsAriaLabel: "파일 상세",
- fileDetailsHeading: "파일 상세",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "이 도구에서 지원하지 않는 파일 형식입니다",
diff --git a/packages/shared/src/i18n/nl.ts b/packages/shared/src/i18n/nl.ts
index 441cbbce..e519ecdf 100644
--- a/packages/shared/src/i18n/nl.ts
+++ b/packages/shared/src/i18n/nl.ts
@@ -36,7 +36,9 @@ export const nl: TranslationKeys = {
unexpectedError: "Er is een onverwachte fout opgetreden.",
retry: "Opnieuw proberen",
privacyPolicy: "Privacybeleid",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Basistools",
optimization: "Optimalisatie",
@@ -1701,6 +1703,9 @@ export const nl: TranslationKeys = {
githubLink: "GitHub-repository",
docsLink: "Documentatie",
apiRefLink: "API-referentie (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1843,7 +1848,9 @@ export const nl: TranslationKeys = {
pipelineName: "Pipeline-naam",
pipelineDescription: "Beschrijving (optioneel)",
noStepsPrompt: "Voeg stappen toe om je automatisering te bouwen",
- step: "Stap",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Stap",
},
nav: {
tools: "Tools",
@@ -1856,10 +1863,29 @@ export const nl: TranslationKeys = {
grid: "Raster",
},
files: {
+ myFiles: "My Files",
recentTab: "Recent",
- uploadTab: "Uploaden",
- fileDetailsAriaLabel: "Bestandsdetails",
- fileDetailsHeading: "Bestandsdetails",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Dit bestandstype wordt niet ondersteund door deze tool",
diff --git a/packages/shared/src/i18n/pl.ts b/packages/shared/src/i18n/pl.ts
index 5b5ed803..34f5a797 100644
--- a/packages/shared/src/i18n/pl.ts
+++ b/packages/shared/src/i18n/pl.ts
@@ -36,7 +36,9 @@ export const pl: TranslationKeys = {
unexpectedError: "Wystąpił nieoczekiwany błąd.",
retry: "Ponów",
privacyPolicy: "Polityka prywatności",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Podstawowe",
optimization: "Optymalizacja",
@@ -1707,6 +1709,9 @@ export const pl: TranslationKeys = {
githubLink: "Repozytorium GitHub",
docsLink: "Dokumentacja",
apiRefLink: "Dokumentacja API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1850,7 +1855,9 @@ export const pl: TranslationKeys = {
pipelineName: "Nazwa Pipeline",
pipelineDescription: "Opis (opcjonalnie)",
noStepsPrompt: "Dodaj kroki, aby zbudować automatyzację",
- step: "Krok",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Krok",
},
nav: {
tools: "Narzędzia",
@@ -1863,10 +1870,29 @@ export const pl: TranslationKeys = {
grid: "Siatka",
},
files: {
- recentTab: "Ostatnie",
- uploadTab: "Przesyłanie",
- fileDetailsAriaLabel: "Szczegóły pliku",
- fileDetailsHeading: "Szczegóły pliku",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Ten typ pliku nie jest obsługiwany przez to narzędzie",
diff --git a/packages/shared/src/i18n/pt-BR.ts b/packages/shared/src/i18n/pt-BR.ts
index 426da5df..b4be05a5 100644
--- a/packages/shared/src/i18n/pt-BR.ts
+++ b/packages/shared/src/i18n/pt-BR.ts
@@ -36,7 +36,9 @@ export const ptBR: TranslationKeys = {
unexpectedError: "Ocorreu um erro inesperado.",
retry: "Tentar novamente",
privacyPolicy: "Politica de privacidade",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Essenciais",
optimization: "Otimizacao",
@@ -1700,6 +1702,9 @@ export const ptBR: TranslationKeys = {
githubLink: "Repositorio no GitHub",
docsLink: "Documentacao",
apiRefLink: "Referencia da API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1843,7 +1848,9 @@ export const ptBR: TranslationKeys = {
pipelineName: "Nome do Pipeline",
pipelineDescription: "Descricao (opcional)",
noStepsPrompt: "Adicione passos para construir sua automacao",
- step: "Passo",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Passo",
},
nav: {
tools: "Ferramentas",
@@ -1856,10 +1863,29 @@ export const ptBR: TranslationKeys = {
grid: "Grade",
},
files: {
- recentTab: "Recentes",
- uploadTab: "Enviar",
- fileDetailsAriaLabel: "Detalhes do arquivo",
- fileDetailsHeading: "Detalhes do arquivo",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Este tipo de arquivo nao e suportado por esta ferramenta",
diff --git a/packages/shared/src/i18n/ru.ts b/packages/shared/src/i18n/ru.ts
index 235fd1ca..217f3efb 100644
--- a/packages/shared/src/i18n/ru.ts
+++ b/packages/shared/src/i18n/ru.ts
@@ -36,7 +36,9 @@ export const ru: TranslationKeys = {
unexpectedError: "Произошла непредвиденная ошибка.",
retry: "Повторить",
privacyPolicy: "Политика конфиденциальности",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Основные",
optimization: "Оптимизация",
@@ -1700,6 +1702,9 @@ export const ru: TranslationKeys = {
githubLink: "Репозиторий GitHub",
docsLink: "Документация",
apiRefLink: "Справочник API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1842,7 +1847,9 @@ export const ru: TranslationKeys = {
pipelineName: "Название Pipeline",
pipelineDescription: "Описание (необязательно)",
noStepsPrompt: "Добавьте шаги для построения автоматизации",
- step: "Шаг",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Шаг",
},
nav: {
tools: "Инструменты",
@@ -1855,10 +1862,29 @@ export const ru: TranslationKeys = {
grid: "Сетка",
},
files: {
- recentTab: "Недавние",
- uploadTab: "Загрузка",
- fileDetailsAriaLabel: "Сведения о файле",
- fileDetailsHeading: "Сведения о файле",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Этот тип файла не поддерживается данным инструментом",
diff --git a/packages/shared/src/i18n/sv.ts b/packages/shared/src/i18n/sv.ts
index f8b62c23..6a508cb0 100644
--- a/packages/shared/src/i18n/sv.ts
+++ b/packages/shared/src/i18n/sv.ts
@@ -36,7 +36,9 @@ export const sv: TranslationKeys = {
unexpectedError: "Ett ovantat fel uppstod.",
retry: "Försök igen",
privacyPolicy: "Integritetspolicy",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Grundlaggande",
optimization: "Optimering",
@@ -1696,6 +1698,9 @@ export const sv: TranslationKeys = {
githubLink: "GitHub-arkiv",
docsLink: "Dokumentation",
apiRefLink: "API-referens (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1837,7 +1842,9 @@ export const sv: TranslationKeys = {
pipelineName: "Pipeline-namn",
pipelineDescription: "Beskrivning (valfritt)",
noStepsPrompt: "Lagg till steg for att bygga din automatisering",
- step: "Steg",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Steg",
},
nav: {
tools: "Verktyg",
@@ -1850,10 +1857,29 @@ export const sv: TranslationKeys = {
grid: "Rutnat",
},
files: {
- recentTab: "Senaste",
- uploadTab: "Ladda upp",
- fileDetailsAriaLabel: "Fildetaljer",
- fileDetailsHeading: "Fildetaljer",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Denna filtyp stods inte av detta verktyg",
diff --git a/packages/shared/src/i18n/th.ts b/packages/shared/src/i18n/th.ts
index db43c7b2..cf3d897a 100644
--- a/packages/shared/src/i18n/th.ts
+++ b/packages/shared/src/i18n/th.ts
@@ -36,7 +36,9 @@ export const th: TranslationKeys = {
unexpectedError: "เกิดข้อผิดพลาดที่ไม่คาดคิด",
retry: "ลองใหม่",
privacyPolicy: "นโยบายความเป็นส่วนตัว",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "พื้นฐาน",
optimization: "การเพิ่มประสิทธิภาพ",
@@ -1674,6 +1676,9 @@ export const th: TranslationKeys = {
githubLink: "คลังเก็บโค้ด GitHub",
docsLink: "เอกสารประกอบ",
apiRefLink: "อ้างอิง API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1814,7 +1819,9 @@ export const th: TranslationKeys = {
pipelineName: "ชื่อ Pipeline",
pipelineDescription: "คำอธิบาย (ไม่บังคับ)",
noStepsPrompt: "เพิ่มขั้นตอนเพื่อสร้างการทำงานอัตโนมัติ",
- step: "ขั้นตอน",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "ขั้นตอน",
},
nav: {
tools: "เครื่องมือ",
@@ -1827,10 +1834,29 @@ export const th: TranslationKeys = {
grid: "กริด",
},
files: {
- recentTab: "ล่าสุด",
- uploadTab: "อัปโหลด",
- fileDetailsAriaLabel: "รายละเอียดไฟล์",
- fileDetailsHeading: "รายละเอียดไฟล์",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "ไฟล์ประเภทนี้ไม่รองรับโดยเครื่องมือนี้",
diff --git a/packages/shared/src/i18n/tr.ts b/packages/shared/src/i18n/tr.ts
index a43c3a24..a3fcf512 100644
--- a/packages/shared/src/i18n/tr.ts
+++ b/packages/shared/src/i18n/tr.ts
@@ -36,7 +36,9 @@ export const tr: TranslationKeys = {
unexpectedError: "Beklenmeyen bir hata oluştu.",
retry: "Yeniden dene",
privacyPolicy: "Gizlilik Politikası",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Temel Araçlar",
optimization: "Optimizasyon",
@@ -1704,6 +1706,9 @@ export const tr: TranslationKeys = {
githubLink: "GitHub Deposu",
docsLink: "Dokümantasyon",
apiRefLink: "API Referansı (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1847,7 +1852,9 @@ export const tr: TranslationKeys = {
pipelineName: "Pipeline Adı",
pipelineDescription: "Açıklama (isteğe bağlı)",
noStepsPrompt: "Otomasyonunuzu oluşturmak için adımlar ekleyin",
- step: "Adım",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Adım",
},
nav: {
tools: "Araçlar",
@@ -1860,10 +1867,29 @@ export const tr: TranslationKeys = {
grid: "Izgara",
},
files: {
- recentTab: "Son Kullanılanlar",
- uploadTab: "Yükle",
- fileDetailsAriaLabel: "Dosya Ayrıntıları",
- fileDetailsHeading: "Dosya Ayrıntıları",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Bu dosya türü bu araç tarafından desteklenmiyor",
diff --git a/packages/shared/src/i18n/uk.ts b/packages/shared/src/i18n/uk.ts
index f63d4d31..0f94de30 100644
--- a/packages/shared/src/i18n/uk.ts
+++ b/packages/shared/src/i18n/uk.ts
@@ -36,7 +36,9 @@ export const uk: TranslationKeys = {
unexpectedError: "Сталася неочікувана помилка.",
retry: "Повторити",
privacyPolicy: "Політика конфіденційності",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Основні",
optimization: "Оптимізація",
@@ -1700,6 +1702,9 @@ export const uk: TranslationKeys = {
githubLink: "Репозиторій GitHub",
docsLink: "Документація",
apiRefLink: "Довідник API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1843,7 +1848,9 @@ export const uk: TranslationKeys = {
pipelineName: "Назва Pipeline",
pipelineDescription: "Опис (необов'язково)",
noStepsPrompt: "Додайте кроки для побудови автоматизації",
- step: "Крок",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Крок",
},
nav: {
tools: "Інструменти",
@@ -1856,10 +1863,29 @@ export const uk: TranslationKeys = {
grid: "Сітка",
},
files: {
- recentTab: "Нещодавні",
- uploadTab: "Завантаження",
- fileDetailsAriaLabel: "Відомості про файл",
- fileDetailsHeading: "Відомості про файл",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Цей тип файлу не підтримується цим інструментом",
diff --git a/packages/shared/src/i18n/vi.ts b/packages/shared/src/i18n/vi.ts
index c1b4f568..489c1a39 100644
--- a/packages/shared/src/i18n/vi.ts
+++ b/packages/shared/src/i18n/vi.ts
@@ -36,7 +36,9 @@ export const vi: TranslationKeys = {
unexpectedError: "Đã xảy ra lỗi không mong muốn.",
retry: "Thử lại",
privacyPolicy: "Chính sách bảo mật",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "Cơ bản",
optimization: "Tối ưu hóa",
@@ -1696,6 +1698,9 @@ export const vi: TranslationKeys = {
githubLink: "Kho mã nguồn GitHub",
docsLink: "Tài liệu",
apiRefLink: "Tham chiếu API (Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1837,7 +1842,9 @@ export const vi: TranslationKeys = {
pipelineName: "Tên Pipeline",
pipelineDescription: "Mô tả (tùy chọn)",
noStepsPrompt: "Thêm các bước để xây dựng tự động hóa",
- step: "Bước",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "Bước",
},
nav: {
tools: "Công cụ",
@@ -1850,10 +1857,29 @@ export const vi: TranslationKeys = {
grid: "Lưới",
},
files: {
- recentTab: "Gần đây",
- uploadTab: "Tải lên",
- fileDetailsAriaLabel: "Chi tiết tệp",
- fileDetailsHeading: "Chi tiết tệp",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "Loại tệp này không được công cụ này hỗ trợ",
diff --git a/packages/shared/src/i18n/zh-CN.ts b/packages/shared/src/i18n/zh-CN.ts
index 4a9b30e3..8cf3d29b 100644
--- a/packages/shared/src/i18n/zh-CN.ts
+++ b/packages/shared/src/i18n/zh-CN.ts
@@ -36,7 +36,9 @@ export const zhCN: TranslationKeys = {
unexpectedError: "发生了意外错误。",
retry: "重试",
privacyPolicy: "隐私政策",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "基础工具",
optimization: "优化",
@@ -1626,6 +1628,9 @@ export const zhCN: TranslationKeys = {
githubLink: "GitHub 仓库",
docsLink: "文档",
apiRefLink: "API 参考(Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1765,7 +1770,9 @@ export const zhCN: TranslationKeys = {
pipelineName: "Pipeline 名称",
pipelineDescription: "描述(可选)",
noStepsPrompt: "添加步骤来构建自动化流程",
- step: "步骤",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "步骤",
},
nav: {
tools: "工具",
@@ -1778,10 +1785,29 @@ export const zhCN: TranslationKeys = {
grid: "网格",
},
files: {
- recentTab: "最近",
- uploadTab: "上传",
- fileDetailsAriaLabel: "文件详情",
- fileDetailsHeading: "文件详情",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "此工具不支持该文件类型",
diff --git a/packages/shared/src/i18n/zh-TW.ts b/packages/shared/src/i18n/zh-TW.ts
index 9aa7be1a..7a6bbf4a 100644
--- a/packages/shared/src/i18n/zh-TW.ts
+++ b/packages/shared/src/i18n/zh-TW.ts
@@ -36,7 +36,9 @@ export const zhTW: TranslationKeys = {
unexpectedError: "發生了非預期的錯誤。",
retry: "重試",
privacyPolicy: "隱私權政策",
- },
+ pageNotFound: "Page not found",
+ pageNotFoundDescription: "The page you are looking for does not exist or has been moved.",
+},
categories: {
essentials: "基本工具",
optimization: "最佳化",
@@ -1624,6 +1626,9 @@ export const zhTW: TranslationKeys = {
githubLink: "GitHub儲存庫",
docsLink: "說明文件",
apiRefLink: "API參考(Swagger)",
+ licenseLabel: "License:",
+ licenseDescription:
+ "SnapOtter is open-source software licensed under the GNU Affero General Public License v3 (AGPLv3).",
},
},
auth: {
@@ -1763,7 +1768,9 @@ export const zhTW: TranslationKeys = {
pipelineName: "Pipeline名稱",
pipelineDescription: "描述(選填)",
noStepsPrompt: "加入步驟來建構自動化",
- step: "步驟",
+ noStepsHeading: "No steps yet",
+ searchToolsPlaceholder: "Search tools...",
+step: "步驟",
},
nav: {
tools: "工具",
@@ -1776,10 +1783,29 @@ export const zhTW: TranslationKeys = {
grid: "格線",
},
files: {
- recentTab: "最近",
- uploadTab: "上傳",
- fileDetailsAriaLabel: "檔案詳情",
- fileDetailsHeading: "檔案詳情",
+ myFiles: "My Files",
+ recentTab: "Recent",
+ uploadTab: "Upload Files",
+ fileDetailsAriaLabel: "File Details",
+ fileDetailsHeading: "File Details",
+ searchPlaceholder: "Search files...",
+ selectedCount: "{count} selected",
+ fileCount: "{count} files",
+ fileCountSingular: "{count} file",
+ noFilesFound: "No files found",
+ selectFilePrompt: "Select a file to view details",
+ openFile: "Open File",
+ openInPipeline: "Open in Pipeline",
+ name: "Name",
+ format: "Format",
+ size: "Size",
+ dimensions: "Dimensions",
+ version: "Version",
+ toolsUsed: "Tools Used",
+ none: "None",
+ delete: "Delete",
+ pipeline: "Pipeline",
+ download: "Download",
},
dropzone: {
unsupportedFileType: "此工具不支援該檔案類型",
diff --git a/tests/e2e/gui-qa-fixes.spec.ts b/tests/e2e/gui-qa-fixes.spec.ts
new file mode 100644
index 00000000..46313430
--- /dev/null
+++ b/tests/e2e/gui-qa-fixes.spec.ts
@@ -0,0 +1,85 @@
+import { expect, test } from "./helpers";
+
+test.describe("QA fixes verification", () => {
+ test("invalid tool slug shows 404 page with Go Home link", async ({ loggedInPage: page }) => {
+ await page.goto("/nonexistent-tool-slug-xyz");
+ await expect(page.locator("text=Tool not found").or(page.locator("text=404"))).toBeVisible({
+ timeout: 10_000,
+ });
+ const goHome = page.getByRole("link", { name: /go home/i });
+ await expect(goHome).toBeVisible();
+ await goHome.click();
+ await expect(page).toHaveURL("/");
+ });
+
+ test("multi-segment invalid URL shows 404 page", async ({ loggedInPage: page }) => {
+ await page.goto("/some/deep/nested/path");
+ await expect(page.locator("text=404")).toBeVisible({ timeout: 10_000 });
+ await expect(page.getByRole("link", { name: /go home/i })).toBeVisible();
+ });
+
+ test("/tools/:toolId redirects to /:toolId", async ({ loggedInPage: page }) => {
+ await page.goto("/tools/resize");
+ await page.waitForURL("**/resize", { timeout: 5_000 });
+ await expect(page).toHaveURL(/\/resize$/);
+ });
+
+ test("confirm password field has visibility toggle", async ({ loggedInPage: page }) => {
+ await page.goto("/");
+ // Open settings
+ const settingsBtn = page.locator('[class*="sidebar"]').getByRole("button").last();
+ await settingsBtn.click().catch(() => {});
+ // Try to navigate to Security tab
+ const securityTab = page.getByText("Security");
+ if (await securityTab.isVisible({ timeout: 3_000 }).catch(() => false)) {
+ await securityTab.click();
+ // Find all password eye toggle buttons
+ const eyeButtons = page.locator('button[tabindex="-1"]');
+ const count = await eyeButtons.count();
+ // Should have at least 3 eye buttons (current, new, confirm)
+ expect(count).toBeGreaterThanOrEqual(3);
+ }
+ });
+
+ test("pipeline steps survive navigation", async ({ loggedInPage: page }) => {
+ await page.goto("/automate");
+ await page.waitForLoadState("networkidle");
+
+ // Add a step by clicking a tool in the palette
+ const resizeTool = page.locator("text=Resize").first();
+ if (await resizeTool.isVisible({ timeout: 5_000 }).catch(() => false)) {
+ await resizeTool.click();
+ // Wait for the step to appear
+ await page.waitForTimeout(500);
+
+ // Navigate away
+ await page.goto("/");
+ await page.waitForLoadState("networkidle");
+
+ // Navigate back
+ await page.goto("/automate");
+ await page.waitForLoadState("networkidle");
+
+ // Steps should still be there (persisted in sessionStorage)
+ const removeButtons = page.locator('button[title="Remove"], button:has-text("Remove")');
+ const count = await removeButtons.count();
+ expect(count).toBeGreaterThanOrEqual(1);
+ }
+ });
+
+ test("export dialog has filename input", async ({ loggedInPage: page }) => {
+ await page.goto("/editor");
+ await page.waitForLoadState("networkidle");
+
+ // Try to open export dialog via keyboard
+ await page.keyboard.press("Control+Shift+S");
+ await page.waitForTimeout(1000);
+
+ const filenameInput = page.locator('input[placeholder="export"]');
+ if (await filenameInput.isVisible({ timeout: 3_000 }).catch(() => false)) {
+ await expect(filenameInput).toBeVisible();
+ await filenameInput.fill("my-image");
+ await expect(filenameInput).toHaveValue("my-image");
+ }
+ });
+});