+
{toTitleCase(getSubcategoryLabel(t, sc.subcategoryId))}
{renderToolButtons(
@@ -192,4 +192,4 @@ const ToolPicker = ({
);
};
-export default ToolPicker;
+export default memo(ToolPicker);
diff --git a/frontend/src/core/components/tools/toolPicker/ToolButton.tsx b/frontend/src/core/components/tools/toolPicker/ToolButton.tsx
index d4021d794a..a0c01c6449 100644
--- a/frontend/src/core/components/tools/toolPicker/ToolButton.tsx
+++ b/frontend/src/core/components/tools/toolPicker/ToolButton.tsx
@@ -1,4 +1,4 @@
-import React from "react";
+import React, { memo } from "react";
import { Button, Badge } from "@mantine/core";
import { useTranslation } from "react-i18next";
import { Tooltip } from "@app/components/shared/Tooltip";
@@ -10,7 +10,10 @@ import FitText from "@app/components/shared/FitText";
import { useHotkeys } from "@app/contexts/HotkeyContext";
import HotkeyDisplay from "@app/components/hotkeys/HotkeyDisplay";
import FavoriteStar from "@app/components/tools/toolPicker/FavoriteStar";
-import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
+import {
+ useToolWorkflowActions,
+ useToolWorkflowData,
+} from "@app/contexts/ToolWorkflowContext";
import type { ToolId } from "@app/types/toolId";
import {
getToolDisabledReason,
@@ -46,7 +49,8 @@ const ToolButton: React.FC
= ({
const { t } = useTranslation();
const { config } = useAppConfig();
const premiumEnabled = config?.premiumEnabled;
- const { isFavorite, toggleFavorite, toolAvailability } = useToolWorkflow();
+ const { isFavorite, toolAvailability } = useToolWorkflowData();
+ const { toggleFavorite } = useToolWorkflowActions();
const disabledReason = getToolDisabledReason(
id,
tool,
@@ -309,4 +313,4 @@ const ToolButton: React.FC = ({
);
};
-export default ToolButton;
+export default memo(ToolButton);
diff --git a/frontend/src/core/components/viewer/ActiveDocumentContext.tsx b/frontend/src/core/components/viewer/ActiveDocumentContext.tsx
index a9029abda8..07c035593c 100644
--- a/frontend/src/core/components/viewer/ActiveDocumentContext.tsx
+++ b/frontend/src/core/components/viewer/ActiveDocumentContext.tsx
@@ -1,6 +1,7 @@
import React, {
createContext,
useContext,
+ useMemo,
useState,
useEffect,
useRef,
@@ -63,8 +64,10 @@ export function ActiveDocumentProvider({
};
}, []);
+ const value = useMemo(() => ({ documentId }), [documentId]);
+
return (
-
+
{children}
);
diff --git a/frontend/src/core/contexts/AppConfigContext.tsx b/frontend/src/core/contexts/AppConfigContext.tsx
index 021508c36c..7635345540 100644
--- a/frontend/src/core/contexts/AppConfigContext.tsx
+++ b/frontend/src/core/contexts/AppConfigContext.tsx
@@ -1,6 +1,7 @@
import React, {
createContext,
useContext,
+ useMemo,
useState,
useEffect,
ReactNode,
@@ -219,12 +220,15 @@ export const AppConfigProvider: React.FC = ({
const refetch = useCallback(() => fetchConfig(true), [fetchConfig]);
- const value: AppConfigContextValue = {
- config,
- loading,
- error,
- refetch,
- };
+ const value = useMemo(
+ () => ({
+ config,
+ loading,
+ error,
+ refetch,
+ }),
+ [config, loading, error, refetch],
+ );
return (
diff --git a/frontend/src/core/contexts/BannerContext.tsx b/frontend/src/core/contexts/BannerContext.tsx
index 241387fc02..f910fe5ec9 100644
--- a/frontend/src/core/contexts/BannerContext.tsx
+++ b/frontend/src/core/contexts/BannerContext.tsx
@@ -1,4 +1,4 @@
-import { createContext, useContext, useState, ReactNode } from "react";
+import { createContext, useContext, useMemo, useState, ReactNode } from "react";
interface BannerContextType {
banner: ReactNode;
@@ -10,10 +10,10 @@ const BannerContext = createContext(undefined);
export function BannerProvider({ children }: { children: ReactNode }) {
const [banner, setBanner] = useState(null);
+ const value = useMemo(() => ({ banner, setBanner }), [banner]);
+
return (
-
- {children}
-
+ {children}
);
}
diff --git a/frontend/src/core/contexts/CommentAuthorContext.tsx b/frontend/src/core/contexts/CommentAuthorContext.tsx
index 1378d628f9..9c3f9cc2ff 100644
--- a/frontend/src/core/contexts/CommentAuthorContext.tsx
+++ b/frontend/src/core/contexts/CommentAuthorContext.tsx
@@ -1,4 +1,4 @@
-import { createContext, useContext, type ReactNode } from "react";
+import { createContext, useContext, useMemo, type ReactNode } from "react";
export interface CommentAuthorValue {
displayName: string;
@@ -15,8 +15,9 @@ export function CommentAuthorProvider({
children: ReactNode;
displayName?: string;
}) {
+ const value = useMemo(() => ({ displayName }), [displayName]);
return (
-
+
{children}
);
diff --git a/frontend/src/core/contexts/ToolWorkflowContext.tsx b/frontend/src/core/contexts/ToolWorkflowContext.tsx
index 60f4124aaf..6bc61fa294 100644
--- a/frontend/src/core/contexts/ToolWorkflowContext.tsx
+++ b/frontend/src/core/contexts/ToolWorkflowContext.tsx
@@ -10,6 +10,7 @@ import React, {
useCallback,
useMemo,
useEffect,
+ useRef,
} from "react";
import {
useToolManagement,
@@ -122,6 +123,46 @@ if (!existingContext) {
(globalThis as any)[__GLOBAL_CONTEXT_KEY__] = ToolWorkflowContext;
}
+/**
+ * Slim subset contexts for hot consumers that don't need the full state.
+ *
+ * - `useToolWorkflowActions()` — referentially-stable callbacks.
+ * - `useToolWorkflowData()` — tool registry, availability, favorites.
+ */
+export interface ToolWorkflowActionsValue {
+ selectTool: (toolId: ToolId | null) => void;
+ clearToolSelection: () => void;
+ toggleFavorite: (toolId: ToolId) => void;
+ handleToolSelect: (toolId: ToolId) => void;
+ handleToolSelectForced: (toolId: ToolId) => void;
+ handleBackToTools: () => void;
+ handleReaderToggle: () => void;
+ setSidebarsVisible: (visible: boolean) => void;
+ setLeftPanelView: (view: "toolPicker" | "toolContent" | "hidden") => void;
+ setReaderMode: (mode: boolean) => void;
+ setToolPanelMode: (mode: ToolPanelMode) => void;
+ setPreviewFile: (file: File | null) => void;
+ setPageEditorFunctions: (functions: PageEditorFunctions | null) => void;
+ setSearchQuery: (query: string) => void;
+ registerToolReset: (toolId: string, resetFunction: () => void) => void;
+ resetTool: (toolId: string) => void;
+}
+
+export interface ToolWorkflowDataValue {
+ toolAvailability: ToolAvailabilityMap;
+ toolRegistry: Partial;
+ favoriteTools: ToolId[];
+ getSelectedTool: (toolId: ToolId | null) => ToolRegistryEntry | null;
+ isFavorite: (toolId: ToolId) => boolean;
+}
+
+const ToolWorkflowActionsContext = createContext<
+ ToolWorkflowActionsValue | undefined
+>(undefined);
+const ToolWorkflowDataContext = createContext<
+ ToolWorkflowDataValue | undefined
+>(undefined);
+
// Provider component
interface ToolWorkflowProviderProps {
children: React.ReactNode;
@@ -534,6 +575,124 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
true,
);
+ // Ref-backed wrappers so callback identities stay stable across renders.
+ const handleToolSelectRef = useRef(handleToolSelect);
+ handleToolSelectRef.current = handleToolSelect;
+ const stableHandleToolSelect = useCallback(
+ (id: ToolId) => handleToolSelectRef.current(id),
+ [],
+ );
+
+ const handleToolSelectForcedRef = useRef(handleToolSelectForced);
+ handleToolSelectForcedRef.current = handleToolSelectForced;
+ const stableHandleToolSelectForced = useCallback(
+ (id: ToolId) => handleToolSelectForcedRef.current(id),
+ [],
+ );
+
+ const handleBackToToolsRef = useRef(handleBackToTools);
+ handleBackToToolsRef.current = handleBackToTools;
+ const stableHandleBackToTools = useCallback(
+ () => handleBackToToolsRef.current(),
+ [],
+ );
+
+ const handleReaderToggleRef = useRef(handleReaderToggle);
+ handleReaderToggleRef.current = handleReaderToggle;
+ const stableHandleReaderToggle = useCallback(
+ () => handleReaderToggleRef.current(),
+ [],
+ );
+
+ const setReaderModeRef = useRef(setReaderMode);
+ setReaderModeRef.current = setReaderMode;
+ const stableSetReaderMode = useCallback(
+ (mode: boolean) => setReaderModeRef.current(mode),
+ [],
+ );
+
+ const setPreviewFileRef = useRef(setPreviewFile);
+ setPreviewFileRef.current = setPreviewFile;
+ const stableSetPreviewFile = useCallback(
+ (file: File | null) => setPreviewFileRef.current(file),
+ [],
+ );
+
+ const setToolPanelModeRef = useRef(setToolPanelMode);
+ setToolPanelModeRef.current = setToolPanelMode;
+ const stableSetToolPanelMode = useCallback(
+ (mode: ToolPanelMode) => setToolPanelModeRef.current(mode),
+ [],
+ );
+
+ const selectToolRef = useRef(actions.setSelectedTool);
+ selectToolRef.current = actions.setSelectedTool;
+ const stableSelectTool = useCallback(
+ (id: ToolId | null) => selectToolRef.current(id),
+ [],
+ );
+
+ const stableClearToolSelection = useCallback(
+ () => selectToolRef.current(null),
+ [],
+ );
+
+ const actionsValue = useMemo(
+ () => ({
+ selectTool: stableSelectTool,
+ clearToolSelection: stableClearToolSelection,
+ toggleFavorite,
+ handleToolSelect: stableHandleToolSelect,
+ handleToolSelectForced: stableHandleToolSelectForced,
+ handleBackToTools: stableHandleBackToTools,
+ handleReaderToggle: stableHandleReaderToggle,
+ setSidebarsVisible,
+ setLeftPanelView,
+ setReaderMode: stableSetReaderMode,
+ setToolPanelMode: stableSetToolPanelMode,
+ setPreviewFile: stableSetPreviewFile,
+ setPageEditorFunctions,
+ setSearchQuery,
+ registerToolReset,
+ resetTool,
+ }),
+ [
+ stableSelectTool,
+ stableClearToolSelection,
+ toggleFavorite,
+ stableHandleToolSelect,
+ stableHandleToolSelectForced,
+ stableHandleBackToTools,
+ stableHandleReaderToggle,
+ setSidebarsVisible,
+ setLeftPanelView,
+ stableSetReaderMode,
+ stableSetToolPanelMode,
+ stableSetPreviewFile,
+ setPageEditorFunctions,
+ setSearchQuery,
+ registerToolReset,
+ resetTool,
+ ],
+ );
+
+ const dataValue = useMemo(
+ () => ({
+ toolAvailability,
+ toolRegistry,
+ favoriteTools,
+ getSelectedTool,
+ isFavorite,
+ }),
+ [
+ toolAvailability,
+ toolRegistry,
+ favoriteTools,
+ getSelectedTool,
+ isFavorite,
+ ],
+ );
+
// Properly memoized context value
const contextValue = useMemo(
(): ToolWorkflowContextValue => ({
@@ -617,12 +776,38 @@ export function ToolWorkflowProvider({ children }: ToolWorkflowProviderProps) {
);
return (
-
- {children}
-
+
+
+
+ {children}
+
+
+
);
}
+/** Tool-workflow callbacks with referentially-stable identities. */
+export function useToolWorkflowActions(): ToolWorkflowActionsValue {
+ const ctx = useContext(ToolWorkflowActionsContext);
+ if (!ctx) {
+ throw new Error(
+ "useToolWorkflowActions must be used within ToolWorkflowProvider",
+ );
+ }
+ return ctx;
+}
+
+/** Tool registry, availability, and favorites — stable while unchanged. */
+export function useToolWorkflowData(): ToolWorkflowDataValue {
+ const ctx = useContext(ToolWorkflowDataContext);
+ if (!ctx) {
+ throw new Error(
+ "useToolWorkflowData must be used within ToolWorkflowProvider",
+ );
+ }
+ return ctx;
+}
+
// Custom hook to use the context
export function useToolWorkflow(): ToolWorkflowContextValue {
const context = useContext(ToolWorkflowContext);
diff --git a/frontend/src/core/hooks/useToolManagement.tsx b/frontend/src/core/hooks/useToolManagement.tsx
index da9377b8ae..87f9b2f889 100644
--- a/frontend/src/core/hooks/useToolManagement.tsx
+++ b/frontend/src/core/hooks/useToolManagement.tsx
@@ -177,11 +177,7 @@ export const useToolManagement = (): ToolManagementResult => {
if (preferences.hideUnavailableTools && (!isAvailable || isComingSoon)) {
return;
}
- availableToolRegistry[toolKey] = {
- ...baseTool,
- name: baseTool.name,
- description: baseTool.description,
- };
+ availableToolRegistry[toolKey] = baseTool;
});
return availableToolRegistry;
}, [baseRegistry, preferences.hideUnavailableTools, toolAvailability]);
diff --git a/frontend/src/core/hooks/useToolNavigation.ts b/frontend/src/core/hooks/useToolNavigation.ts
index e733850ac9..5e40000fa6 100644
--- a/frontend/src/core/hooks/useToolNavigation.ts
+++ b/frontend/src/core/hooks/useToolNavigation.ts
@@ -1,6 +1,6 @@
import { useCallback } from "react";
import { ToolRegistryEntry, getToolUrlPath } from "@app/data/toolsTaxonomy";
-import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
+import { useToolWorkflowActions } from "@app/contexts/ToolWorkflowContext";
import { handleUnlessSpecialClick } from "@app/utils/clickHandlers";
import { ToolId } from "@app/types/toolId";
@@ -21,7 +21,7 @@ export function useToolNavigation(): {
tool: ToolRegistryEntry,
) => ToolNavigationProps;
} {
- const { handleToolSelect } = useToolWorkflow();
+ const { handleToolSelect } = useToolWorkflowActions();
const getToolNavigation = useCallback(
(toolId: string, tool: ToolRegistryEntry): ToolNavigationProps => {
diff --git a/frontend/src/desktop/components/tools/toolPicker/ToolButton.tsx b/frontend/src/desktop/components/tools/toolPicker/ToolButton.tsx
index ba1ecf8e0e..39b71f9d38 100644
--- a/frontend/src/desktop/components/tools/toolPicker/ToolButton.tsx
+++ b/frontend/src/desktop/components/tools/toolPicker/ToolButton.tsx
@@ -1,7 +1,10 @@
-import { useState, useEffect } from "react";
+import { memo, useState, useEffect } from "react";
import CoreToolButton from "@core/components/tools/toolPicker/ToolButton";
import { getToolDisabledReason } from "@app/components/tools/fullscreen/shared";
-import { useToolWorkflow } from "@app/contexts/ToolWorkflowContext";
+import {
+ useToolWorkflowActions,
+ useToolWorkflowData,
+} from "@app/contexts/ToolWorkflowContext";
import { useAppConfig } from "@app/contexts/AppConfigContext";
import {
connectionModeService,
@@ -18,7 +21,8 @@ type CoreToolButtonProps = React.ComponentProps;
* In selfhosted/saas mode the tool renders as visually unavailable (dimmed, no badge).
*/
const ToolButton: React.FC = (props) => {
- const { toolAvailability, handleToolSelectForced } = useToolWorkflow();
+ const { toolAvailability } = useToolWorkflowData();
+ const { handleToolSelectForced } = useToolWorkflowActions();
const { config } = useAppConfig();
const premiumEnabled = config?.premiumEnabled;
const [connectionMode, setConnectionMode] = useState(
@@ -55,4 +59,4 @@ const ToolButton: React.FC = (props) => {
);
};
-export default ToolButton;
+export default memo(ToolButton);
From 2c15044dfa755747a576ec2b6634e8005b2fa4a3 Mon Sep 17 00:00:00 2001
From: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Date: Tue, 19 May 2026 19:13:58 +0100
Subject: [PATCH 03/20] Snag/disable logo drag (#6393)
---
frontend/src/core/styles/index.css | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/frontend/src/core/styles/index.css b/frontend/src/core/styles/index.css
index 9298fd4147..66a98cc8c8 100644
--- a/frontend/src/core/styles/index.css
+++ b/frontend/src/core/styles/index.css
@@ -15,6 +15,11 @@ body {
-moz-osx-font-smoothing: grayscale;
}
+img {
+ -webkit-user-drag: none;
+ user-drag: none;
+}
+
code {
font-family:
source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
From 2399da689386c2fbc891a1ad0f06f033ab101439 Mon Sep 17 00:00:00 2001
From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Date: Tue, 19 May 2026 23:03:44 +0100
Subject: [PATCH 04/20] migrate exportUpdatedPages from bytes to stream (#6201)
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
---
.../api/converters/ConvertPdfJsonController.java | 13 ++++++++-----
.../SPDF/service/PdfJsonConversionService.java | 11 +++++++----
2 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java
index d68cfad726..371e63ffa5 100644
--- a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java
+++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java
@@ -184,8 +184,6 @@ public class ConvertPdfJsonController {
validateJobAccess(jobId);
- byte[] pdfBytes = pdfJsonConversionService.exportUpdatedPages(jobId, document);
-
String baseName =
(filename != null && !filename.isBlank())
? FILE_EXTENSION_PATTERN
@@ -197,13 +195,18 @@ public class ConvertPdfJsonController {
.orElse("document");
String docName = baseName.endsWith(".pdf") ? baseName : baseName + ".pdf";
TempFile tempOut = tempFileManager.createManagedTempFile(".pdf");
- try {
- Files.write(tempOut.getPath(), pdfBytes);
+ try (OutputStream os = Files.newOutputStream(tempOut.getPath())) {
+ pdfJsonConversionService.exportUpdatedPages(jobId, document, os);
+ } catch (Exception e) {
+ tempOut.close();
+ throw e;
+ }
+ try {
+ return WebResponseUtils.pdfFileToWebResponse(tempOut, docName);
} catch (Exception e) {
tempOut.close();
throw e;
}
- return WebResponseUtils.pdfFileToWebResponse(tempOut, docName);
}
@GetMapping(value = "/pdf/text-editor/page/{jobId}/{pageNumber}")
diff --git a/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java
index 331c79008e..1eed1d2759 100644
--- a/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java
+++ b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java
@@ -6490,7 +6490,8 @@ public class PdfJsonConversionService {
objectMapper.writeValue(out, pageFonts);
}
- public byte[] exportUpdatedPages(String jobId, PdfJsonDocument updates) throws IOException {
+ public void exportUpdatedPages(String jobId, PdfJsonDocument updates, OutputStream outputStream)
+ throws IOException {
if (jobId == null || jobId.isBlank()) {
throw new IllegalArgumentException("jobId is required for incremental export");
}
@@ -6513,7 +6514,8 @@ public class PdfJsonConversionService {
log.debug(
"Incremental export requested with no page updates; returning cached PDF for jobId {}",
jobId);
- return cached.getPdfBytes();
+ outputStream.write(cached.getPdfBytes());
+ return;
}
try (PDDocument document = pdfDocumentFactory.load(cached.getPdfBytes(), true)) {
@@ -6580,7 +6582,8 @@ public class PdfJsonConversionService {
log.debug(
"Incremental export for jobId {} resulted in no page updates; returning cached PDF",
jobId);
- return cached.getPdfBytes();
+ outputStream.write(cached.getPdfBytes());
+ return;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -6603,7 +6606,7 @@ public class PdfJsonConversionService {
"Incremental export complete for jobId {} (pages updated: {})",
jobId,
updatedPages.stream().map(i -> i + 1).sorted().toList());
- return updatedBytes;
+ outputStream.write(updatedBytes);
}
}
From b146d9994d0c7a22381665165de83e932a5ad9a6 Mon Sep 17 00:00:00 2001
From: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
Date: Wed, 20 May 2026 13:57:41 +0100
Subject: [PATCH 05/20] fix(task): make `task dev` / `task dev:all` work on
Windows (#6392)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
## Summary
`#6145` (port picker) and `#6244` (gradle unification) combined to break
`task dev` / `task dev:all` on Windows. Two independent regressions,
both addressed here.
### 1. `find-free-port.ps1` panic (`#6145`)
```
$ task --dry dev
The argument 'scriptsfind-free-port.ps1' to the -File parameter does not exist.
panic: ended up with a non-nil exitStatus.err but a zero exitStatus.code
```
- **Backslash stripped.** `Taskfile.yml` had
`scripts\find-free-port.ps1`. go-task pipes the `sh:` block through
mvdan/sh, which treats `\` as a POSIX escape and silently drops it,
leaving `scriptsfind-free-port.ps1`. PowerShell can't find the file,
exits non-zero, mvdan/sh panics on the inconsistent exit status.
Switched to a forward slash.
- **`-Preferred 8080,5173` is brittle.** Relies on PowerShell parsing
the comma-list into `[int[]]`. Dropped the named flag; switched the
script's `param` to `[Parameter(ValueFromRemainingArguments =
$true)][int[]]$Preferred`; pass each port as its own positional token
(matching how the bash variant is called).
### 2. `bash gradlew` can't find Java on Windows (`#6244`)
```
[backend:dev] ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
```
`#6244` unified all gradle invocations under `bash gradlew` on the
assumption that Git-Bash inherits Windows-side env. It doesn't (and
neither does WSL bash, which can also shadow `bash` on a developer's
PATH). The standard Adoptium/Temurin installer sets `JAVA_HOME` in the
Windows env only, so `bash gradlew` fails before Spring Boot even loads.
Restored the per-platform branch for every backend task: `cmd /c
".\gradlew.bat ..."` on Windows, `./gradlew ...` on Linux/macOS. Two
Windows-specific gotchas to be aware of for future edits:
- mvdan/sh strips `\` outside of double quotes, so `cmd /c
.\gradlew.bat` ends up as `.gradlew.bat`. The entire payload must be
wrapped in double quotes (`cmd /c "..."`).
- Modern Windows excludes cwd from cmd.exe's search path, so the leading
`.\` is required — bare `cmd /c gradlew.bat` errors with "is not
recognized" even from the repo root.
## Test plan
- [x] `task --dry dev` / `task --dry dev:all` on Windows resolve ports
cleanly and dispatch the inner tasks
- [x] `powershell -NoProfile -File scripts/find-free-port.ps1 8080 5173
5001` prints three ports
- [x] `task backend:dev PORT=8081` on Windows starts Spring Boot in ~9s;
`/api/v1/info/status` returns `{"status":"UP"}`
- [x] `task dev:all` on Windows brings up all three services healthy:
backend `/api/v1/info/status` 200, engine `/health` 200, frontend `/`
200
- [ ] Linux / macOS path unaffected (only the Windows branches changed
in behaviour)
---
.taskfiles/backend.yml | 81 +++++++++++++++++++++++++++++++-------
Taskfile.yml | 6 +--
scripts/find-free-port.ps1 | 2 +-
3 files changed, 70 insertions(+), 19 deletions(-)
diff --git a/.taskfiles/backend.yml b/.taskfiles/backend.yml
index 4fdc50a165..56db8eb4ac 100644
--- a/.taskfiles/backend.yml
+++ b/.taskfiles/backend.yml
@@ -1,7 +1,19 @@
version: '3'
-# All gradle invocations go through `bash gradlew` (the sh wrapper) so they work
-# uniformly on Linux/macOS (native bash) and Windows-with-Git-Bash.
+# Gradle invocation strategy:
+# - Linux/macOS: `./gradlew` runs the POSIX shell wrapper natively.
+# - Windows: `cmd /c ".\gradlew.bat ..."` invokes the .bat wrapper through
+# cmd.exe so it inherits the user's Windows-side `JAVA_HOME`
+# and PATH. Routing through `bash gradlew` on Windows ends up
+# under WSL or Git-Bash, neither of which inherits
+# Adoptium/Temurin's default Windows-only Java env - gradlew
+# then errors with "JAVA_HOME is not set and no 'java' command
+# could be found".
+#
+# The entire `.\gradlew.bat ...` payload is double-quoted so
+# the leading `.\` survives mvdan/sh's POSIX backslash
+# stripping; cmd.exe also requires `.\` (not bare `gradlew.bat`)
+# because modern Windows excludes cwd from cmd's search path.
tasks:
dev:
@@ -13,43 +25,67 @@ tasks:
env:
SERVER_PORT: '{{.PORT}}'
cmds:
- - '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}bash gradlew :stirling-pdf:bootRun'
+ - cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
+ platforms: [windows]
+ - cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED=true {{end}}./gradlew :stirling-pdf:bootRun'
+ platforms: [linux, darwin]
dev:bundled:
desc: "Clean + bootRun with frontend bundled into the backend (single :8080 server)"
ignore_error: true
cmds:
- - bash gradlew clean bootRun -PbuildWithFrontend=true
+ - cmd: cmd /c ".\gradlew.bat clean bootRun -PbuildWithFrontend=true"
+ platforms: [windows]
+ - cmd: ./gradlew clean bootRun -PbuildWithFrontend=true
+ platforms: [linux, darwin]
build:
desc: "Full backend build"
cmds:
- - bash gradlew clean build
+ - cmd: cmd /c ".\gradlew.bat clean build"
+ platforms: [windows]
+ - cmd: ./gradlew clean build
+ platforms: [linux, darwin]
build:fast:
desc: "Build without tests"
cmds:
- - bash gradlew clean build -x test
+ - cmd: cmd /c ".\gradlew.bat clean build -x test"
+ platforms: [windows]
+ - cmd: ./gradlew clean build -x test
+ platforms: [linux, darwin]
build:ci:
desc: "Build for CI (formatting checked separately)"
cmds:
- - bash gradlew build -PnoSpotless
+ - cmd: cmd /c ".\gradlew.bat build -PnoSpotless"
+ platforms: [windows]
+ - cmd: ./gradlew build -PnoSpotless
+ platforms: [linux, darwin]
test:
desc: "Run backend tests"
cmds:
- - bash gradlew test
+ - cmd: cmd /c ".\gradlew.bat test"
+ platforms: [windows]
+ - cmd: ./gradlew test
+ platforms: [linux, darwin]
format:
desc: "Auto-fix code formatting"
cmds:
- - bash gradlew spotlessApply
+ - cmd: cmd /c ".\gradlew.bat spotlessApply"
+ platforms: [windows]
+ - cmd: ./gradlew spotlessApply
+ platforms: [linux, darwin]
format:check:
desc: "Check code formatting"
cmds:
- - bash gradlew spotlessCheck
+ - cmd: cmd /c ".\gradlew.bat spotlessCheck"
+ platforms: [windows]
+ - cmd: ./gradlew spotlessCheck
+ platforms: [linux, darwin]
fix:
desc: "Auto-fix backend"
@@ -59,7 +95,10 @@ tasks:
swagger:
desc: "Generate OpenAPI docs"
cmds:
- - bash gradlew :stirling-pdf:copySwaggerDoc
+ - cmd: cmd /c ".\gradlew.bat :stirling-pdf:copySwaggerDoc"
+ platforms: [windows]
+ - cmd: ./gradlew :stirling-pdf:copySwaggerDoc
+ platforms: [linux, darwin]
sources:
- app/core/src/main/java/**/*.java
- app/proprietary/src/main/java/**/*.java
@@ -77,19 +116,31 @@ tasks:
desc: "Print project version"
silent: true
cmds:
- - bash gradlew printVersion --quiet | tail -1
+ - cmd: cmd /c ".\gradlew.bat printVersion --quiet" | tail -1
+ platforms: [windows]
+ - cmd: ./gradlew printVersion --quiet | tail -1
+ platforms: [linux, darwin]
licenses:check:
desc: "Check dependency licenses"
cmds:
- - bash gradlew checkLicense --no-parallel
+ - cmd: cmd /c ".\gradlew.bat checkLicense --no-parallel"
+ platforms: [windows]
+ - cmd: ./gradlew checkLicense --no-parallel
+ platforms: [linux, darwin]
licenses:generate:
desc: "Check and generate dependency license report"
cmds:
- - bash gradlew checkLicense generateLicenseReport --no-parallel
+ - cmd: cmd /c ".\gradlew.bat checkLicense generateLicenseReport --no-parallel"
+ platforms: [windows]
+ - cmd: ./gradlew checkLicense generateLicenseReport --no-parallel
+ platforms: [linux, darwin]
clean:
desc: "Clean build artifacts"
cmds:
- - bash gradlew clean
+ - cmd: cmd /c ".\gradlew.bat clean"
+ platforms: [windows]
+ - cmd: ./gradlew clean
+ platforms: [linux, darwin]
diff --git a/Taskfile.yml b/Taskfile.yml
index a4b71dce32..7edf1e9b41 100644
--- a/Taskfile.yml
+++ b/Taskfile.yml
@@ -3,7 +3,7 @@ version: '3'
output: prefixed
vars:
- FIND_FREE_PORT_PS: powershell -NoProfile -File scripts\find-free-port.ps1 -Preferred
+ FIND_FREE_PORT_PS: powershell -NoProfile -File scripts/find-free-port.ps1
FIND_FREE_PORT_SH: bash scripts/find-free-port.sh
includes:
@@ -45,7 +45,7 @@ tasks:
desc: "Start backend + frontend concurrently on free ports"
vars:
PORTS:
- sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080,5173{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173{{end}}'
+ sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173{{end}}'
BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}'
deps:
@@ -62,7 +62,7 @@ tasks:
desc: "Start backend + frontend + engine concurrently on free ports"
vars:
PORTS:
- sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080,5173,5001{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5001{{end}}'
+ sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173 5001{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5001{{end}}'
BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}'
ENGINE_PORT: '{{index (splitList "\n" .PORTS) 2}}'
diff --git a/scripts/find-free-port.ps1 b/scripts/find-free-port.ps1
index 7b6697456d..241117a148 100644
--- a/scripts/find-free-port.ps1
+++ b/scripts/find-free-port.ps1
@@ -4,7 +4,7 @@
# emits a random free port in 20000-49999. Probes by attempting to bind a
# TcpListener on loopback. Tracks picks within this run so outputs are
# guaranteed distinct from each other.
-param([int[]]$Preferred)
+param([Parameter(ValueFromRemainingArguments = $true)][int[]]$Preferred)
$script:picked = @()
From dd770dfa76899a8f661642d59156dd24bfcb3c7a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:10:56 +0100
Subject: [PATCH 06/20] build(deps): bump rustls-webpki from 0.103.9 to
0.103.13 in /frontend/src-tauri (#6323)
Signed-off-by: dependabot[bot]
---
frontend/src-tauri/Cargo.lock | 19 ++++++++++++++++---
1 file changed, 16 insertions(+), 3 deletions(-)
diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock
index 87d5531f1e..a85a95a564 100644
--- a/frontend/src-tauri/Cargo.lock
+++ b/frontend/src-tauri/Cargo.lock
@@ -1966,7 +1966,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
- "windows-core 0.61.2",
+ "windows-core 0.62.2",
]
[[package]]
@@ -3865,9 +3865,9 @@ dependencies = [
[[package]]
name = "rustls-webpki"
-version = "0.103.9"
+version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53"
+checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [
"ring",
"rustls-pki-types",
@@ -6041,6 +6041,19 @@ dependencies = [
"windows-core 0.61.2",
]
+[[package]]
+name = "windows-core"
+version = "0.61.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
+dependencies = [
+ "windows-implement",
+ "windows-interface",
+ "windows-link 0.1.3",
+ "windows-result 0.3.4",
+ "windows-strings 0.4.2",
+]
+
[[package]]
name = "windows-core"
version = "0.62.2"
From 2790467241297e44475dead27de11936c90eb077 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:11:11 +0100
Subject: [PATCH 07/20] build(deps): bump urllib3 from 2.6.3 to 2.7.0 in
/testing/cucumber in the pip group across 1 directory (#6352)
Signed-off-by: dependabot[bot]
---
testing/cucumber/requirements.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/testing/cucumber/requirements.txt b/testing/cucumber/requirements.txt
index 93e38e0e7d..1b86edcae3 100644
--- a/testing/cucumber/requirements.txt
+++ b/testing/cucumber/requirements.txt
@@ -337,7 +337,7 @@ six==1.17.0 \
# via
# behave
# parse-type
-urllib3==2.6.3 \
- --hash=sha256:1b62b6884944a57dbe321509ab94fd4d3b307075e0c2eae991ac71ee15ad38ed \
- --hash=sha256:bf272323e553dfb2e87d9bfd225ca7b0f467b919d7bbd355436d3fd37cb0acd4
+urllib3==2.7.0 \
+ --hash=sha256:231e0ec3b63ceb14667c67be60f2f2c40a518cb38b03af60abc813da26505f4c \
+ --hash=sha256:9fb4c81ebbb1ce9531cce37674bbc6f1360472bc18ca9a553ede278ef7276897
# via requests
From 4c3be4992fb37a01ca087e6c95ba100e8688444f Mon Sep 17 00:00:00 2001
From: Ludy
Date: Wed, 20 May 2026 15:11:43 +0200
Subject: [PATCH 08/20] deps: align Bouncy Castle modules to shared version
property (#6362)
---
build.gradle | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/build.gradle b/build.gradle
index f20ad18c55..ec0a8c5dfb 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,7 +24,7 @@ ext {
pdfboxVersion = "3.0.7"
imageioVersion = "3.13.1"
lombokVersion = "1.18.46"
- bouncycastleVersion = "1.83"
+ bouncycastleVersion = "1.84"
springSecuritySamlVersion = "7.0.5"
openSamlVersion = "5.2.1"
commonmarkVersion = "0.28.0"
@@ -197,7 +197,6 @@ subprojects {
exclude group: 'org.bouncycastle', module: 'bcutil-jdk15on'
exclude group: 'org.bouncycastle', module: 'bcmail-jdk15on'
-
// Security CVE fixes - hardcoded resolution strategy to ensure safe versions
// Primary fixes via explicit dependencies in app/core/build.gradle:
// - CVE-2022-25647: gson 2.8.9+ (explicit dependency overrides tabula 2.8.7)
@@ -209,9 +208,10 @@ subprojects {
resolutionStrategy.force 'org.apache.commons:commons-lang3:3.20.0'
// CVE-2024-47554: commons-io 2.21.0 DoS prevention
resolutionStrategy.force 'commons-io:commons-io:2.21.0'
- // Bouncycastle 1.83 (from bouncycastleVersion variable)
- resolutionStrategy.force 'org.bouncycastle:bcprov-jdk18on:1.83'
- resolutionStrategy.force 'org.bouncycastle:bcpkix-jdk18on:1.83'
+ // Keep BouncyCastle modules aligned to avoid runtime linkage errors
+ resolutionStrategy.force "org.bouncycastle:bcprov-jdk18on:${bouncycastleVersion}"
+ resolutionStrategy.force "org.bouncycastle:bcpkix-jdk18on:${bouncycastleVersion}"
+ resolutionStrategy.force "org.bouncycastle:bcutil-jdk18on:${bouncycastleVersion}"
}
dependencyManagement {
From 8fb281525b6d49f1fbd611bbfd565f4878c9e249 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:13:31 +0100
Subject: [PATCH 09/20] build(deps): bump quinn-proto from 0.11.13 to 0.11.14
in /frontend/src-tauri (#6322)
Signed-off-by: dependabot[bot]
---
frontend/src-tauri/Cargo.lock | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock
index a85a95a564..d96f1a3420 100644
--- a/frontend/src-tauri/Cargo.lock
+++ b/frontend/src-tauri/Cargo.lock
@@ -3380,9 +3380,9 @@ dependencies = [
[[package]]
name = "quinn-proto"
-version = "0.11.13"
+version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f1906b49b0c3bc04b5fe5d86a77925ae6524a19b816ae38ce1e426255f1d8a31"
+checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"bytes",
"getrandom 0.3.4",
@@ -5115,7 +5115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82a72c767771b47409d2345987fda8628641887d5466101319899796367354a0"
dependencies = [
"fastrand",
- "getrandom 0.3.4",
+ "getrandom 0.4.1",
"once_cell",
"rustix",
"windows-sys 0.59.0",
From 178744310270bdeeb7e70c228325a35646ad416a Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:15:08 +0100
Subject: [PATCH 10/20] build(deps): bump fast-uri from 3.0.6 to 3.1.2 in
/devTools (#6332)
Signed-off-by: dependabot[bot]
---
devTools/package-lock.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/devTools/package-lock.json b/devTools/package-lock.json
index c747a84299..2ea1628978 100644
--- a/devTools/package-lock.json
+++ b/devTools/package-lock.json
@@ -555,9 +555,9 @@
}
},
"node_modules/fast-uri": {
- "version": "3.0.6",
- "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz",
- "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==",
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz",
+ "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==",
"dev": true,
"funding": [
{
From 1f5c04636266f0f40b7e897bf5bb4c06e36033cb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:16:18 +0100
Subject: [PATCH 11/20] build(deps): bump tokio from 1.49.0 to 1.50.0 in
/frontend/src-tauri in the tokio group across 1 directory (#6296)
Bumps the tokio group with 1 update in the /frontend/src-tauri
directory: [tokio](https://github.com/tokio-rs/tokio).
Updates `tokio` from 1.49.0 to 1.50.0
Release notes
Sourced from tokio's
releases.
Tokio v1.50.0
1.50.0 (Mar 3rd, 2026)
Added
- net: add
TcpStream::set_zero_linger (#7837)
- rt: add
is_rt_shutdown_err (#7771)
Changed
- io: add optimizer hint that
memchr returns in-bounds
pointer (#7792)
- io: implement vectored writes for
write_buf (#7871)
- runtime: panic when
event_interval is set to 0 (#7838)
- runtime: shorten default thread name to fit in Linux limit (#7880)
- signal: remember the result of
SetConsoleCtrlHandler
(#7833)
- signal: specialize windows
Registry (#7885)
Fixed
- io: always cleanup
AsyncFd registration list on
deregister (#7773)
- macros: remove (most) local
use declarations in
tokio::select! (#7929)
- net: fix
GET_BUF_SIZE constant for target_os =
"android" (#7889)
- runtime: avoid redundant unpark in current_thread scheduler (#7834)
- runtime: don't park in
current_thread if
before_park defers waker (#7835)
- io: fix write readiness on ESP32 on short writes (#7872)
- runtime: wake deferred tasks before entering
block_in_place (#7879)
- sync: drop rx waker when oneshot receiver is dropped (#7886)
- runtime: fix double increment of
num_idle_threads on
shutdown (#7910,
#7918,
#7922)
Unstable
- fs: check for io-uring opcode support (#7815)
- runtime: avoid lock acquisition after uring init (#7850)
Documented
- docs: update outdated unstable features section (#7839)
- io: clarify the behavior of
AsyncWriteExt::shutdown()
(#7908)
- io: explain how to flush stdout/stderr (#7904)
- io: fix incorrect and confusing
AsyncWrite
documentation (#7875)
- rt: clarify the documentation of
Runtime::spawn (#7803)
- rt: fix missing quotation in docs (#7925)
- runtime: correct the default thread name in docs (#7896)
- runtime: fix
event_interval doc (#7932)
- sync: clarify RwLock fairness documentation (#7919)
- sync: clarify that
recv returns None once
closed and no more messages (#7920)
- task: clarify when to use
spawn_blocking vs dedicated
threads (#7923)
- task: doc that task drops before
JoinHandle completion
(#7825)
- signal: guarantee that listeners never return
None (#7869)
- task: fix task module feature flags in docs (#7891)
... (truncated)
Commits
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
frontend/src-tauri/Cargo.lock | 16 ++++++++--------
frontend/src-tauri/Cargo.toml | 2 +-
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock
index d96f1a3420..e16dba6a6d 100644
--- a/frontend/src-tauri/Cargo.lock
+++ b/frontend/src-tauri/Cargo.lock
@@ -972,7 +972,7 @@ dependencies = [
"libc",
"option-ext",
"redox_users",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -1195,7 +1195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [
"libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -2871,7 +2871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
dependencies = [
"libc",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -3824,7 +3824,7 @@ dependencies = [
"errno",
"libc",
"linux-raw-sys",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -5118,7 +5118,7 @@ dependencies = [
"getrandom 0.4.1",
"once_cell",
"rustix",
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
@@ -5263,9 +5263,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.49.0"
+version = "1.50.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72a2903cd7736441aac9df9d7688bd0ce48edccaadf181c3b90be801e81d3d86"
+checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d"
dependencies = [
"bytes",
"libc",
@@ -5995,7 +5995,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [
- "windows-sys 0.59.0",
+ "windows-sys 0.61.2",
]
[[package]]
diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml
index 789832a401..64fc0bcf33 100644
--- a/frontend/src-tauri/Cargo.toml
+++ b/frontend/src-tauri/Cargo.toml
@@ -37,7 +37,7 @@ tauri-plugin-deep-link = "2.4.6"
tauri-plugin-notification = "2.3.3"
tauri-plugin-window-state = "2.2.1"
keyring = { version = "3.6.1", features = ["apple-native", "windows-native"] }
-tokio = { version = "1.0", features = ["time", "sync"] }
+tokio = { version = "1.50", features = ["time", "sync"] }
reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls", "rustls-tls-native-roots"] }
tiny_http = "0.12"
url = "2.5"
From f00c12ad8a48f6c1c4f7eb422701489aae7c646f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:16:38 +0100
Subject: [PATCH 12/20] build(deps): bump step-security/harden-runner from
2.19.0 to 2.19.1 (#6380)
Signed-off-by: dependabot[bot]
---
.github/workflows/test-build-docker.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test-build-docker.yml b/.github/workflows/test-build-docker.yml
index 10d35c4405..5e6ddb67b1 100644
--- a/.github/workflows/test-build-docker.yml
+++ b/.github/workflows/test-build-docker.yml
@@ -165,7 +165,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
- uses: step-security/harden-runner@8d3c67de8e2fe68ef647c8db1e6a09f647780f40 # v2.19.0
+ uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
with:
egress-policy: audit
From 31b00b7d06f77c466e16ab554785d483e6aa9753 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:16:42 +0100
Subject: [PATCH 13/20] build(deps): bump github/codeql-action from 4.35.3 to
4.35.4 (#6379)
Signed-off-by: dependabot[bot]
---
.github/workflows/scorecards.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml
index a2a4164159..b2aaae9feb 100644
--- a/.github/workflows/scorecards.yml
+++ b/.github/workflows/scorecards.yml
@@ -75,6 +75,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
- uses: github/codeql-action/upload-sarif@e46ed2cbd01164d986452f91f178727624ae40d7 # v3.29.5
+ uses: github/codeql-action/upload-sarif@68bde559dea0fdcac2102bfdf6230c5f70eb485e # v3.29.5
with:
sarif_file: results.sarif
From d76d25654a2ccd796af43a520dbf347737d15927 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:16:48 +0100
Subject: [PATCH 14/20] build(deps): bump actions/dependency-review-action from
4.9.0 to 5.0.0 (#6378)
Signed-off-by: dependabot[bot]
---
.github/workflows/dependency-review.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml
index 89009ed6e9..2dc3647b88 100644
--- a/.github/workflows/dependency-review.yml
+++ b/.github/workflows/dependency-review.yml
@@ -21,6 +21,6 @@ jobs:
- name: "Checkout Repository"
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: "Dependency Review"
- uses: actions/dependency-review-action@2031cfc080254a8a887f58cffee85186f0e49e48 # v4.9.0
+ uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0
with:
config-file: "./.github/config/dependency-review-config.yml"
From a4ef70eaf8dad03cb1e26cd952d55f66bc290610 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:17:04 +0100
Subject: [PATCH 15/20] build(deps): bump sigstore/cosign-installer from 4.1.1
to 4.1.2 (#6377)
Signed-off-by: dependabot[bot]
---
.github/workflows/push-docker-base.yml | 2 +-
.github/workflows/push-docker.yml | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/.github/workflows/push-docker-base.yml b/.github/workflows/push-docker-base.yml
index 0b9869de14..a132df2b24 100644
--- a/.github/workflows/push-docker-base.yml
+++ b/.github/workflows/push-docker-base.yml
@@ -100,7 +100,7 @@ jobs:
sbom: true
- name: Install cosign
- uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
+ uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v2.4.1"
diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml
index b760000a11..937ca0d09c 100644
--- a/.github/workflows/push-docker.yml
+++ b/.github/workflows/push-docker.yml
@@ -96,13 +96,13 @@ jobs:
- name: Install cosign
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master'
- uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
+ uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v2.4.1"
- name: Install cosign
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master'
- uses: sigstore/cosign-installer@cad07c2e89fa2edd6e2d7bab4c1aa38e53f76003 # v4.1.1
+ uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v2.4.1"
From 5a5aada2d82d7d9264e4977c45b12705b532603f Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:17:18 +0100
Subject: [PATCH 16/20] build(deps): bump pypdf from 6.10.2 to 6.11.0 in
/testing/cucumber (#6376)
Signed-off-by: dependabot[bot]
---
testing/cucumber/requirements.txt | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/testing/cucumber/requirements.txt b/testing/cucumber/requirements.txt
index 1b86edcae3..537c16f65c 100644
--- a/testing/cucumber/requirements.txt
+++ b/testing/cucumber/requirements.txt
@@ -315,9 +315,9 @@ pycryptodome==3.23.0 \
--hash=sha256:e3f2d0aaf8080bda0587d58fc9fe4766e012441e2eed4269a77de6aea981c8be \
--hash=sha256:eb8f24adb74984aa0e5d07a2368ad95276cf38051fe2dc6605cbcf482e04f2a7
# via -r requirements.in
-pypdf==6.10.2 \
- --hash=sha256:7d09ce108eff6bf67465d461b6ef352dcb8d84f7a91befc02f904455c6eea11d \
- --hash=sha256:aa53be9826655b51c96741e5d7983ca224d898ac0a77896e64636810517624aa
+pypdf==6.11.0 \
+ --hash=sha256:062b51c81b0910e6d2755e99e1c5547a0a23b7d0a32322af66240d8edcfabe87 \
+ --hash=sha256:769394d5756d5b304c9b6bef88b54b1816b328e7e6fc9254e625529a15ed4ab8
# via -r requirements.in
qrcode==8.2 \
--hash=sha256:16e64e0716c14960108e85d853062c9e8bba5ca8252c0b4d0231b9df4060ff4f \
From ea91a355184256f72a093453eb3214935c3e72ff Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:17:36 +0100
Subject: [PATCH 17/20] build(deps): bump org.postgresql:postgresql from
42.7.10 to 42.7.11 (#6375)
Signed-off-by: dependabot[bot]
---
app/proprietary/build.gradle | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/proprietary/build.gradle b/app/proprietary/build.gradle
index e79a440246..e923ae6229 100644
--- a/app/proprietary/build.gradle
+++ b/app/proprietary/build.gradle
@@ -67,7 +67,7 @@ dependencies {
runtimeOnly "io.jsonwebtoken:jjwt-impl:$jwtVersion"
runtimeOnly "io.jsonwebtoken:jjwt-jackson:$jwtVersion"
runtimeOnly 'com.h2database:h2:2.3.232' // Don't upgrade h2database - file format incompatible with 2.4.x, would break existing user databases
- runtimeOnly 'org.postgresql:postgresql:42.7.10'
+ runtimeOnly 'org.postgresql:postgresql:42.7.11'
implementation('com.coveo:saml-client:5.0.0') {
exclude group: 'org.opensaml', module: 'opensaml-core'
}
From 707cfa3140021f0ed4ed856a9ddd02869fb65ac4 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:18:43 +0100
Subject: [PATCH 18/20] build(deps): bump devalue from 5.6.4 to 5.8.1 in
/frontend (#6367)
Signed-off-by: dependabot[bot]
---
frontend/package-lock.json | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 5808309d65..4f478e2b7a 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -7776,9 +7776,9 @@
}
},
"node_modules/devalue": {
- "version": "5.6.4",
- "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.4.tgz",
- "integrity": "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA==",
+ "version": "5.8.1",
+ "resolved": "https://registry.npmjs.org/devalue/-/devalue-5.8.1.tgz",
+ "integrity": "sha512-4CXDYRBGqN+57wVJkuXBYmpAVUSg3L6JAQa/DFqm238G73E1wuyc/JhGQJzN7vUf/CMphYau2zXbfWzDR5aTEw==",
"license": "MIT",
"peer": true
},
From 0c3a512b2145df286fd70e0b5165ec3e1efffdeb Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 20 May 2026 14:34:16 +0100
Subject: [PATCH 19/20] build(deps): bump softprops/action-gh-release from
2.6.1 to 3.0.0 (#6346)
Signed-off-by: dependabot[bot]
---
.github/workflows/multiOSReleases.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml
index 10088ac52d..8ef352dfab 100644
--- a/.github/workflows/multiOSReleases.yml
+++ b/.github/workflows/multiOSReleases.yml
@@ -638,7 +638,7 @@ jobs:
run: ls -R ./artifacts
- name: Upload binaries to Release
- uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2.6.1
+ uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0
with:
tag_name: v${{ needs.determine-matrix.outputs.version }}
generate_release_notes: true
From 089de247b4ef72fe967ab13248c0425b8f30b034 Mon Sep 17 00:00:00 2001
From: IberianSoldierPC <56433990+IberianSoldierPC@users.noreply.github.com>
Date: Thu, 21 May 2026 14:16:07 +0200
Subject: [PATCH 20/20] Updated Spanish translation (#6309)
Co-authored-by: Copilot
Co-authored-by: Ludy
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
---
.../public/locales/es-ES/translation.toml | 269 +++++++++---------
scripts/ignore_translation.toml | 103 +++++++
2 files changed, 243 insertions(+), 129 deletions(-)
diff --git a/frontend/public/locales/es-ES/translation.toml b/frontend/public/locales/es-ES/translation.toml
index a6808ed7ef..80c3dc50be 100644
--- a/frontend/public/locales/es-ES/translation.toml
+++ b/frontend/public/locales/es-ES/translation.toml
@@ -1,8 +1,8 @@
accessInvite = "Invitar"
-addToDoc = "Agregar al Documento"
+addToDoc = "Agregar al documento"
alphabet = "Alfabeto"
apply = "Aplicar"
-applyAndContinue = "Aplicar y continuar"
+applyAndContinue = "Guardar y salir"
areYouSure = "¿Seguro que quieres salir?"
back = "Atrás"
black = "Negro"
@@ -25,7 +25,7 @@ confirmCloseSaveFailed = "Guardado con errores. No fue posible guardar {{count}}
confirmCloseSaveFailedTitle = "Error al guardar"
confirmCloseUnsaved = "Este archivo tiene cambios sin guardar."
confirmCloseUnsavedList = "Tienes {{count}} archivo{{plural}} con cambios sin guardar.\n\n{{fileList}}"
-confirmPasswordErrorMessage = "Deben coincidir Nueva Contraseña y Confirmar Nueva Contraseña."
+confirmPasswordErrorMessage = "La nueva contraseña y su confirmación deben coincidir."
custom = "Personalizado..."
customPosition = "Posición personalizada"
customTextTooltip = "Formato personalizado opcional para los números de página. Usa {n} como marcador para el número. Ejemplo: \"Página {n}\" mostrará \"Página 1\", \"Página 2\", etc."
@@ -45,9 +45,9 @@ downloadPdf = "Descargar PDF"
downloadUnavailable = "Descarga no disponible para este elemento"
edit = "Editar"
editSecret = "Editar este valor"
-editYourNewFiles = "Editar sus nuevos archivos"
-encryptedFileBlocked = "El archivo está protegido con contraseña. Desbloquéelo primero."
-encryptedFilesBlocked = "Hay {{count}} archivos protegidos con contraseña. Desbloquéelos primero."
+editYourNewFiles = "Editar tus archivos nuevos"
+encryptedFileBlocked = "El archivo está protegido con contraseña. Desbloquéalo primero."
+encryptedFilesBlocked = "Hay {{count}} archivos protegidos con contraseña. Desbloquéalos primero."
exportAndContinue = "Exportar y continuar"
false = "Falso"
fileNotSavedToDisk = "No guardado en el disco"
@@ -59,7 +59,7 @@ fontSizeTooltip = "Tamaño del texto del número de página en puntos. Números
fontTypeTooltip = "Familia de fuente para los números de página. Elige según el estilo de tu documento."
genericSubmit = "Procesar"
goHomepage = "Ir a la página principal"
-goToPage = "Ir a página"
+goToPage = "Ir"
green = "Verde"
help = "Ayuda"
imgPrompt = "Seleccionar Imagen(es)"
@@ -69,7 +69,7 @@ insufficientCredits = "Créditos insuficientes. Requeridos: {{requiredCredits}},
invalidPasswordMessage = "La contraseña no puede dejarse en blanco y no puede empezar ni terminar con espacios."
invalidUndoData = "No se puede deshacer: datos de la operación no válidos"
invalidUsernameMessage = "Nombre de usuario no válido, el nombre de usuario solo puede contener letras, números y los siguientes caracteres especiales @._+- o debe ser una dirección de correo electrónico válida."
-joinDiscord = "Únase a nuestro servidor Discord"
+joinDiscord = "Únete a nuestro servidor de Discord"
keepWorking = "Seguir trabajando"
loading = "Cargando..."
loadingCredits = "Comprobando créditos..."
@@ -77,26 +77,26 @@ loadingProStatus = "Comprobando estado de suscripción..."
logOut = "Cerrar sesión"
marginTooltip = "Distancia entre el número de página y el borde de la página."
moreOptions = "Más Opciones"
-multiPdfDropPrompt = "Seleccione (o arrastre y suelte) todos los PDF que necesite"
+multiPdfDropPrompt = "Selecciona (o arrastra y suelta) todos los PDF que necesites"
multiPdfPrompt = "Seleccionar PDFs (2+)"
never = "Nunca"
no = "No"
-noFavourites = "No se agregaron favoritos"
-noFileSelected = "No ha seleccionado ningún archivo. Por favor, cargue uno."
+noFavourites = "No hay favoritos añadidos"
+noFileSelected = "No has seleccionado ningún archivo. Por favor, carga uno."
noFilesToUndo = "No se puede deshacer: no se procesaron archivos en la última operación"
noOperationToUndo = "No hay ninguna operación para deshacer"
notAuthenticatedMessage = "Usuario no autenticado."
nothingToUndo = "Nada que deshacer"
-noticeTopUpOrPlan = "No hay suficientes créditos, por favor recarga o actualiza tu plan"
+noticeTopUpOrPlan = "No hay suficientes créditos; recarga o actualiza tu plan"
noValidFiles = "No hay archivos válidos para procesar"
oops = "¡Ups!"
openInViewer = "Abrir en el visor"
operationCancelled = "Operación cancelada"
page = "Página"
pageNum = "Número de página"
-pageOrderPrompt = "Orden personalizado de páginas (Introduzca una lista de números de página separados por coma o funciones como 2n+1):"
+pageOrderPrompt = "Orden personalizado de páginas (Introduce una lista de números de página separados por coma o funciones como 2n+1):"
pages = "Páginas"
-pageSelectionPrompt = "Selección personalizada de páginas (Introduzca una lista de números de página separados por comas 1,5,6 o funciones como 2n+1):"
+pageSelectionPrompt = "Selección personalizada de páginas (Introduce una lista de números de página separados por comas 1,5,6 o funciones como 2n+1):"
password = "Contraseña"
pdfPrompt = "Seleccionar PDF(s)"
pendingRedactions = "Tienes censuras sin aplicar que se perderán."
@@ -128,14 +128,14 @@ true = "Verdadero"
undo = "Deshacer"
undoDataMismatch = "No se puede deshacer: los datos de la operación están corruptos"
undoFailed = "No se pudo deshacer la operación"
-undoOperationTooltip = "Haga clic para deshacer la última operación y restaurar los archivos originales"
+undoOperationTooltip = "Haz clic para deshacer la última operación y restaurar los archivos originales"
undoQuotaError = "No se puede deshacer: espacio de almacenamiento insuficiente"
undoStorageError = "Deshacer completado, pero algunos archivos no se pudieron guardar en el almacenamiento"
undoSuccess = "Operación deshecha correctamente"
unknown = "Desconocido"
unpin = "Desfijar archivo (reemplazar tras ejecutar la herramienta)"
-unsavedChanges = "Tiene cambios sin guardar en su PDF. ¿Qué le gustaría hacer?"
-unsavedChangesTitle = "Cambios sin Guardar"
+unsavedChanges = "Tienes cambios sin guardar en tu PDF. ¿Qué te gustaría hacer?"
+unsavedChangesTitle = "Cambios sin guardar"
unsupported = "No compatible"
uploadLimit = "Tamaño máximo de archivo:"
uploadLimitExceededPlural = "son demasiado grandes. El tamaño máximo permitido es"
@@ -145,24 +145,24 @@ userAlreadyExistsWebMessage = "El usuario ya existe como usuario web."
username = "Nombre de usuario"
usernameExistsMessage = "El nuevo nombre de usuario está en uso."
userNotFoundMessage = "Usuario no encontrado."
-visitGithub = "Visitar Repositorio de GitHub"
+visitGithub = "Visitar el repositorio de GitHub"
welcome = "Bienvenido"
white = "Blanco"
-WorkInProgess = "Trabajo en curso. Puede no funcionar o tener errores. ¡Por favor, informa de cualquier problema!"
+WorkInProgess = "En desarrollo. Puede fallar o tener errores. Informa de cualquier problema."
yes = "Sí"
[account]
accountSettings = "Configuración de la cuenta"
-adminSettings = "Configuración de Administrador - Ver y Añadir Usuarios"
-changePassword = "Cambiar Contraseña"
+adminSettings = "Configuración de administrador: ver y añadir usuarios"
+changePassword = "Cambiar contraseña"
changeUsername = "Cambiar nombre de usuario"
changeUsernameDescription = "Actualiza tu nombre de usuario. Se cerrará tu sesión tras actualizar."
-confirmNewPassword = "Confirmar Nueva Contraseña"
-newPassword = "Nueva Contraseña"
-newUsername = "nuevo nombre de usuario"
+confirmNewPassword = "Confirmar nueva contraseña"
+newPassword = "Nueva contraseña"
+newUsername = "Nuevo nombre de usuario"
newUsernamePlaceholder = "Introduce tu nuevo nombre de usuario"
oldPassword = "Contraseña anterior"
-password = "Confirmar contraseña"
+password = "Contraseña de confirmación"
property = "Propiedad"
settingsCompare = "Comparación de configuraciones:"
signOut = "Cerrar sesión"
@@ -172,7 +172,7 @@ syncToBrowser = "Sincronizar cuenta -> Navegador"
title = "Configuración de la cuenta"
userControlSettings = "Configuración de control de usuario"
webBrowserSettings = "Configuración del navegador"
-yourApiKey = "Su clave API"
+yourApiKey = "Tu clave de API"
[account.mfa]
codeLabel = "Código de autenticación"
@@ -207,7 +207,7 @@ failed = "Se produjo un error al añadir adjuntos al PDF."
[AddAttachmentsRequest]
addMoreFiles = "Añadir más archivos..."
attachments = "Seleccionar archivos adjuntos"
-info = "Seleccione archivos para adjuntar a su PDF. Estos archivos se incrustarán y serán accesibles a través del panel de archivos adjuntos del PDF."
+info = "Selecciona archivos para adjuntar a tu PDF. Estos archivos se incrustarán y serán accesibles a través del panel de archivos adjuntos del PDF."
placeholder = "Elegir archivos..."
selectedFiles = "Archivos seleccionados"
selectFiles = "Seleccionar archivos para adjuntar"
@@ -223,11 +223,11 @@ title = "Resultados de archivos adjuntos"
title = "Qué hace"
[AddAttachmentsRequest.tooltip.header]
-title = "Acerca de Añadir adjuntos"
+title = "Acerca de añadir adjuntos"
[addImage]
applySignatures = "Aplicar imágenes"
-header = "Añadir imagen al PDF"
+header = "Añadir imágenes al PDF"
tags = "img,jpg,imagen,fotografía"
title = "Añadir imagen"
@@ -238,13 +238,13 @@ failed = "Se produjo un error al añadir la imagen al PDF."
hint = "Soporta archivos PNG, JPEG, GIF, BMP, TIFF y SVG. Los archivos SVG se convertirán a PNG para su incrustación."
label = "Archivo de imagen"
name = "Imagen"
-placeholder = "Suba una imagen"
+placeholder = "Sube una imagen"
[addImage.instructions]
-noSignature = "Suba una imagen arriba para habilitar la colocación."
+noSignature = "Sube una imagen para habilitar la colocación."
paused = "Colocación en pausa"
-resumeHint = "Reanude la colocación para hacer clic y añadir su imagen."
-text = "Después de subir su imagen arriba, haga clic en cualquier parte del PDF para colocarla."
+resumeHint = "Reanuda la colocación para hacer clic y añadir tu imagen."
+text = "Después de subir tu imagen, haz clic en cualquier parte del PDF para colocarla."
title = "Cómo añadir imágenes"
[addImage.mode]
@@ -261,9 +261,9 @@ defaultImageLabel = "Imagen subida"
defaultLabel = "Imagen"
[addImage.step]
-createDesc = "Suba la imagen que desea añadir"
+createDesc = "Sube la imagen que quieres añadir"
place = "Colocar imagen"
-placeDesc = "Haga clic en el PDF para añadir su imagen"
+placeDesc = "Haz clic en el PDF para añadir tu imagen"
[addImage.steps]
configure = "Configurar imagen"
@@ -302,7 +302,7 @@ title = "Resultados de numeración de páginas"
[addPassword]
completed = "Protección con contraseña aplicada"
-desc = "Cifre su documento PDF con una contraseña."
+desc = "Cifra tu documento PDF con una contraseña."
filenamePrefix = "cifrado"
submit = "Encriptar"
title = "Proteger con contraseña"
@@ -322,11 +322,11 @@ stepTitle = "Contraseñas y cifrado"
[addPassword.passwords.owner]
label = "Contraseña de propietario"
-placeholder = "Introduzca la contraseña de propietario"
+placeholder = "Introduce la contraseña de propietario"
[addPassword.passwords.user]
label = "Contraseña de usuario"
-placeholder = "Introduzca la contraseña de usuario"
+placeholder = "Introduce la contraseña de usuario"
[addPassword.results]
title = "PDFs cifrados"
@@ -1411,18 +1411,12 @@ title = "Mensajes de retroalimentación"
[admin.settings.telegram.feedback.channel]
title = "Reglas de retroalimentación en canales"
-
-[admin.settings.telegram.feedback.channel.errorMessage]
-description = "Mostrar mensajes de error detallados para los canales."
-label = "Mostrar mensajes de error (Canal)"
-
-[admin.settings.telegram.feedback.channel.errorProcessing]
-description = "Enviar mensajes de error de procesamiento a los canales."
-label = "Mostrar errores de procesamiento (Canal)"
-
-[admin.settings.telegram.feedback.channel.noValidDocument]
-description = "Suprimir la respuesta de sin documento válido para subidas en canales."
-label = "Mostrar \"Sin documento válido\" (Canal)"
+errorMessage.description = "Mostrar mensajes de error detallados para los canales."
+errorMessage.label = "Mostrar mensajes de error (Canal)"
+errorProcessing.description = "Enviar mensajes de error de procesamiento a los canales."
+errorProcessing.label = "Mostrar errores de procesamiento (Canal)"
+noValidDocument.description = "Suprimir la respuesta de sin documento válido para subidas en canales."
+noValidDocument.label = "Mostrar \"Sin documento válido\" (Canal)"
[admin.settings.telegram.feedback.general.enabled]
description = "Controla si el bot envía mensajes de retroalimentación en absoluto."
@@ -1430,18 +1424,12 @@ label = "Habilitar retroalimentación"
[admin.settings.telegram.feedback.user]
title = "Reglas de retroalimentación a usuarios"
-
-[admin.settings.telegram.feedback.user.errorMessage]
-description = "Mostrar mensajes de error detallados para los usuarios."
-label = "Mostrar mensajes de error (Usuario)"
-
-[admin.settings.telegram.feedback.user.errorProcessing]
-description = "Enviar mensajes de error de procesamiento a los usuarios."
-label = "Mostrar errores de procesamiento (Usuario)"
-
-[admin.settings.telegram.feedback.user.noValidDocument]
-description = "Suprimir la respuesta de sin documento válido para subidas de usuarios."
-label = "Mostrar \"Sin documento válido\" (Usuario)"
+errorMessage.description = "Mostrar mensajes de error detallados para los usuarios."
+errorMessage.label = "Mostrar mensajes de error (Usuario)"
+errorProcessing.description = "Enviar mensajes de error de procesamiento a los usuarios."
+errorProcessing.label = "Mostrar errores de procesamiento (Usuario)"
+noValidDocument.description = "Suprimir la respuesta de sin documento válido para subidas de usuarios."
+noValidDocument.label = "Mostrar \"Sin documento válido\" (Usuario)"
[admin.settings.telegram.pipelineInboxFolder]
description = "Carpeta bajo el directorio de pipeline donde se almacenan los archivos entrantes de Telegram."
@@ -1911,16 +1899,32 @@ processImagesDesc = "Convierte varios archivos de imagen en un solo documento PD
securePdfIngestion = "Ingesta segura de PDF"
securePdfIngestionDesc = "Flujo de trabajo integral de procesamiento de PDF que sanea documentos, aplica OCR con limpieza, convierte a formato PDF/A para archivo a largo plazo y optimiza el tamaño del archivo."
secureWorkflow = "Flujo de trabajo de seguridad"
-secureWorkflowDesc = "Asegura documentos PDF eliminando contenido potencialmente malicioso como JavaScript y archivos incrustados, luego añade protección con contraseña para evitar acceso no autorizado. La contraseña se establece en 'password' de forma predeterminada."
+secureWorkflowDesc = "Asegura documentos PDF eliminando contenido potencialmente malicioso como JavaScript y archivos incrustados, luego añade protección con contraseña para evitar accesos no autorizados. La contraseña se establece en 'password' por defecto."
[autoRename]
description = "Esta herramienta renombrará automáticamente los archivos PDF en función de su contenido. Analiza el documento para encontrar el título más adecuado a partir del texto."
+[pdfCommentAgent]
+submit = "Generar comentarios"
+prompt.label = "¿Sobre qué debería comentar la IA?"
+prompt.placeholder = "p. ej., Señala fechas ambiguas y sugiere aclaraciones"
+
+[pdfCommentAgent.settings]
+title = "Instrucciones de comentarios"
+
+[pdfCommentAgent.results]
+title = "PDF con comentarios"
+
+[pdfCommentAgent.error]
+failed = "No se pudieron generar los comentarios"
+emptyPrompt = "Describe sobre qué debería comentar la IA"
+tooLong = "El prompt es demasiado largo (máximo {{max}} caracteres)"
+
[autoSizeSplitPDF]
tags = "pdf,dividir,documento,organización"
[autoSplitPDF]
-description = "Imprimir, Insertar, Escanear, cargar, y déjenos separar automáticamente sus documentos. No se necesita clasificación manual."
+description = "Imprime, inserta, escanea o carga, y deja que separemos automáticamente tus documentos. No se necesita clasificación manual."
dividerDownload2 = "Descargar 'Divisor automático (con instrucciones).pdf'"
duplexMode = "Modo Dúplex (Escaneado de ambas caras)"
formPrompt = "Entregar PDF conteniendo divisores de página de Stirling-PDF:"
@@ -1931,8 +1935,8 @@ title = "Dividir PDF automáticamente"
[autoSplitPDF.selectText]
1 = "Imprimir algunas hojas divisorias desde la parte inferior (Blanco y negro está bien)."
-2 = "Escanee todos sus documentos a la vez insertando la hoja divisoria entre ellos."
-3 = "Cargue un único archivo PDF escaneado de gran tamaño y deje que Stirling PDF se encargue del resto."
+2 = "Escanea todos tus documentos a la vez insertando la hoja divisoria entre ellos."
+3 = "Carga un único archivo PDF escaneado de gran tamaño y deja que Stirling PDF se encargue del resto."
4 = "Las páginas divisorias son automáticamente detectadas y eliminadas, garantizando un buen documento final."
[backendHealth]
@@ -2106,7 +2110,7 @@ title = "Rango"
toPlaceholder = "Hasta"
[bulkSelection.syntax]
-text = "Use números, rangos, palabras clave y progresiones (n comienza en 0). Se admiten paréntesis."
+text = "Usa números, rangos, palabras clave y progresiones (n comienza en 0). Se admiten paréntesis."
title = "Conceptos básicos de sintaxis"
[bulkSelection.syntax.bullets]
@@ -2457,15 +2461,15 @@ stepTitle = "Modo de firma"
[certSign.signMode.tooltip.auto]
text = "Firma con un certificado autofirmado del servidor. Mismo sello contra manipulaciones y registro de auditoría; normalmente muestra No verificado en los visores."
title = "Automático - Configuración cero, sello de sistema instantáneo"
-use = "Usar cuando: necesite velocidad e identidad interna consistente en revisiones y registros."
+use = "Úsalo cuando necesites velocidad e identidad interna consistente en revisiones y registros."
[certSign.signMode.tooltip.header]
title = "Acerca de las firmas PDF"
[certSign.signMode.tooltip.manual]
-text = "Use sus propios archivos de certificado para identidad alineada con su marca. Puede mostrar Confiable cuando su CA/cadena es reconocida."
+text = "Usa tus propios archivos de certificado para una identidad alineada con tu marca. Puede mostrarse Confiable cuando la CA/cadena sea reconocida."
title = "Manual - Traiga su certificado"
-use = "Usar para: atención al cliente, legal, cumplimiento."
+use = "Úsalo para: atención al cliente, legal y cumplimiento."
[certSign.signMode.tooltip.overview]
text = "Ambos modos sellan el documento (cualquier edición se marca como manipulación) y registran quién/cuándo/cómo para auditoría. La confianza del visor depende de la cadena de certificados."
@@ -2481,9 +2485,9 @@ title = "Acerca de la gestión de firmas"
[certSign.tooltip.overview]
bullet1 = "Verificar firmas existentes y su validez"
bullet2 = "Ver información detallada sobre firmantes y certificados"
-bullet3 = "Añadir nuevas firmas digitales para asegurar sus documentos"
+bullet3 = "Añadir nuevas firmas digitales para asegurar tus documentos"
bullet4 = "Múltiples archivos compatibles con navegación fácil"
-text = "Esta herramienta le permite verificar si sus PDFs están firmados digitalmente y añadir nuevas firmas digitales. Las firmas digitales demuestran quién creó o aprobó un documento y muestran si ha sido cambiado desde la firma."
+text = "Esta herramienta te permite verificar si tus PDF están firmados digitalmente y añadir nuevas firmas digitales. Las firmas digitales demuestran quién creó o aprobó un documento y muestran si se ha cambiado desde la firma."
title = "¿Qué puede hacer esta herramienta?"
[certSign.tooltip.signing]
@@ -2500,17 +2504,17 @@ bullet1 = "Muestra si las firmas son válidas o inválidas"
bullet2 = "Muestra información del firmante y fecha de firma"
bullet3 = "Verifica si el documento fue modificado después de firmar"
bullet4 = "Puede usar certificados personalizados para verificación"
-text = "Cuando verifica firmas, la herramienta le indica si son válidas, quién firmó el documento, cuándo se firmó y si el documento ha sido cambiado desde la firma."
+text = "Cuando verificas firmas, la herramienta te indica si son válidas, quién firmó el documento, cuándo se firmó y si el documento se ha cambiado desde la firma."
title = "Verificar firmas"
[changeCreds]
-changePassword = "Está usando las credenciales de inicio de sesión por defecto. Por favor, introduzca una contraseña nueva"
+changePassword = "Estás usando las credenciales de inicio de sesión por defecto. Por favor, introduce una contraseña nueva"
changeUsername = "Actualiza tu nombre de usuario. Se cerrará tu sesión tras actualizar."
-confirmNewPassword = "Confirme la nueva contraseña"
+confirmNewPassword = "Confirma la nueva contraseña"
credsUpdated = "Cuenta actualizada"
description = "Cambios guardados. Vuelve a iniciar sesión."
error = "No se pudo actualizar el nombre de usuario. Verifica tu contraseña e inténtalo de nuevo."
-header = "Actualice los detalles de su cuenta"
+header = "Actualiza los detalles de tu cuenta"
newPassword = "Nueva contraseña"
newUsername = "Nuevo usuario"
oldPassword = "Contraseña actual"
@@ -2682,7 +2686,7 @@ text = "Cambia los permisos del documento, permitiendo/prohibiendo el acceso a d
title = "Cambiar permisos"
[changePermissions.tooltip.warning]
-text = "Para hacer estos permisos inmutables, use la herramienta Agregar Contraseña para establecer una contraseña de propietario."
+text = "Para hacer estos permisos inmutables, usa la herramienta Agregar Contraseña para establecer una contraseña de propietario."
[chat.progress]
analyzing = "Analizando su solicitud..."
@@ -2965,9 +2969,9 @@ deleteFailed = "Error al eliminar la cuenta."
deleteFailedTitle = "No se puede eliminar la cuenta"
deleteWarning = "Esta acción es permanente y no se puede deshacer. Se eliminarán todos tus datos."
enterEmailConfirm = "Para confirmar la eliminación, introduce tu dirección de correo electrónico ({{email}}) a continuación:"
-guestDescription = "Ha iniciado sesión como invitado. Considere actualizar su cuenta arriba."
+guestDescription = "Has iniciado sesión como invitado. Considera actualizar tu cuenta arriba."
label = "Resumen"
-manageAccountPreferences = "Administre las preferencias de su cuenta"
+manageAccountPreferences = "Administra las preferencias de tu cuenta"
signedInAs = "Sesión iniciada como"
title = "Configuración de Cuenta"
@@ -2998,13 +3002,13 @@ title = "Contraseñas y Seguridad"
update = "Actualizar contraseña"
[config.account.upgrade]
-description = "¡Vincule su cuenta para preservar su historial y acceder a más funciones!"
+description = "¡Vincula tu cuenta para preservar tu historial y acceder a más funciones!"
email = "Correo electrónico"
-emailPassword = "o ingrese su correo electrónico y contraseña"
-emailPlaceholder = "Ingrese su correo electrónico"
+emailPassword = "o introduce tu correo electrónico y contraseña"
+emailPlaceholder = "Introduce tu correo electrónico"
linkWith = "Vincular con"
password = "Contraseña (opcional)"
-passwordNote = "Deje vacío para usar solo verificación por correo electrónico"
+passwordNote = "Deja vacío para usar solo verificación por correo electrónico"
passwordPlaceholder = "Establecer una contraseña"
socialLogin = "Actualizar con Cuenta Social"
title = "Actualizar Cuenta de Invitado"
@@ -3014,19 +3018,19 @@ upgradeButton = "Actualizar Cuenta"
chartAriaLabel = "Uso de créditos: incluidos {{includedUsed}} de {{includedTotal}}, comprados {{purchasedUsed}} de {{purchasedTotal}}"
copyKeyAriaLabel = "Copiar clave API"
creditsRemaining = "Créditos Restantes"
-description = "Su clave API para acceder al conjunto de herramientas PDF de Stirling. Cópiela a su proyecto o actualice para generar una nueva."
+description = "Tu clave API para acceder al conjunto de herramientas PDF de Stirling. Cópiala en tu proyecto o actualízala para generar una nueva."
docsDescription = "Más información sobre cómo integrar con Stirling PDF:"
docsLink = "Documentación de la API"
docsTitle = "Documentación de la API"
-generateError = "No pudimos generar su clave API."
+generateError = "No pudimos generar tu clave API."
goToAccount = "Ir a Cuenta"
-guestInfo = "Los usuarios invitados no reciben claves API. Cree una cuenta para obtener una clave API que pueda usar en sus aplicaciones."
+guestInfo = "Los usuarios invitados no reciben claves API. Crea una cuenta para obtener una clave API que puedas usar en tus aplicaciones."
includedCredits = "Créditos incluidos"
intro = "Usa tu clave de API para acceder de forma programática a las capacidades de procesamiento de Stirling PDF."
label = "Clave API"
lastApiUse = "Último Uso de API"
nextReset = "Próximo Restablecimiento"
-overlayMessage = "Genere una clave para ver créditos y créditos disponibles"
+overlayMessage = "Genera una clave para ver créditos y créditos disponibles"
publicKeyAriaLabel = "Clave API pública"
purchasedCredits = "Créditos comprados"
refreshAriaLabel = "Actualizar clave API"
@@ -3045,10 +3049,10 @@ failedToRetrieveApiKey = "No se pudo obtener la clave de API de la respuesta."
[config.apiKeys.refreshModal]
confirmCta = "Actualizar Claves"
-confirmPrompt = "¿Está seguro de que desea continuar?"
-impact = "Cualquier aplicación o servicio que esté utilizando actualmente estas claves dejará de funcionar hasta que las actualice con las nuevas claves."
+confirmPrompt = "¿Estás seguro de que deseas continuar?"
+impact = "Cualquier aplicación o servicio que esté utilizando actualmente estas claves dejará de funcionar hasta que las actualices con las nuevas claves."
title = "Actualizar Claves API"
-warning = "⚠️ Advertencia: Esta acción generará nuevas claves API y hará que sus claves anteriores sean inválidas."
+warning = "⚠️ Advertencia: Esta acción generará nuevas claves API y hará que tus claves anteriores sean inválidas."
[config.overview]
description = "Ajustes actuales de la aplicación y detalles de configuración."
@@ -3124,7 +3128,7 @@ maintainAspectRatio = "Mantener Relación de Aspecto"
markdown = "Markdown"
maxAttachmentSize = "Tamaño máximo de archivo adjunto (MB)"
multiple = "Múltiple"
-noFileSelected = "No se seleccionó ningún archivo. Use el panel de archivos para agregar archivos."
+noFileSelected = "No se seleccionó ningún archivo. Usa el panel de archivos para agregar archivos."
odpExt = "Presentación OpenDocument (.odp)"
odtExt = "Texto OpenDocument (.odt)"
officeDocs = "Documentos de Office (Word, Excel, PowerPoint)"
@@ -3346,7 +3350,7 @@ selectArea = "Seleccionar Área de Recorte"
[crop.tooltip]
description = "Seleccione el área a recortar de su PDF arrastrando y redimensionando la superposición azul en la miniatura."
drag = "Arrastre la superposición para mover el área de recorte"
-precision = "Use entradas de coordenadas para posicionamiento preciso"
+precision = "Usa entradas de coordenadas para un posicionamiento preciso"
resize = "Arrastre las esquinas y bordes para redimensionar"
title = "Cómo Recortar PDFs"
@@ -3451,7 +3455,7 @@ failed = "No se pudo actualizar el índice"
[editTableOfContents.info]
line1 = "Cada marcador necesita un título descriptivo y la página que debe abrir."
-line2 = "Use marcadores hijos para crear una jerarquía para capítulos, secciones o subsecciones."
+line2 = "Usa marcadores hijos para crear una jerarquía de capítulos, secciones o subsecciones."
line3 = "Importe marcadores del PDF seleccionado o de un archivo JSON para ahorrar tiempo."
[editTableOfContents.messages]
@@ -4137,6 +4141,11 @@ desc = "Renombra automáticamente un archivo PDF basándose en su encabezado det
tags = "auto-detectar,basado-en-encabezado,organizar,reetiquetar"
title = "Renombrar Automáticamente Archivo PDF"
+[home.pdfCommentAgent]
+desc = "Pide a la IA que anote un PDF con comentarios de notas adhesivas según tu indicación"
+tags = "IA,agente,comentario,anotar,nota adhesiva,revisión,feedback,notas"
+title = "Añadir comentarios con IA"
+
[home.autoSizeSplitPDF]
desc = "Divide un solo PDF en múltiples documentos según su tamaño, número de páginas, o número de documento"
tags = "auto,dividir,tamaño"
@@ -4539,58 +4548,58 @@ signIn = "Iniciar sesión"
[login]
accountCreatedSuccess = "¡Cuenta creada correctamente! Ya puedes iniciar sesión."
-alreadyLoggedIn = "Ya ha iniciado sesión en"
-alreadyLoggedIn2 = "dispositivos. Cierre sesión en los dispositivos y vuelva a intentarlo."
+alreadyLoggedIn = "Ya has iniciado sesión en"
+alreadyLoggedIn2 = "dispositivos. Cierra sesión en los dispositivos y vuelve a intentarlo."
backToSignIn = "Volver a iniciar sesión"
cancel = "Cancelar"
-changePasswordWarning = "Cambie su contraseña tras iniciar sesión por primera vez"
+changePasswordWarning = "Cambia tu contraseña tras iniciar sesión por primera vez"
credentialsUpdated = "Tus credenciales se han actualizado. Vuelve a iniciar sesión."
debug = "Depurar"
defaultCredentials = "Credenciales de inicio de sesión predeterminadas"
-dontHaveAccount = "¿No tiene una cuenta? Registrarse"
+dontHaveAccount = "¿No tienes una cuenta? Registrarse"
email = "Correo electrónico"
-enterEmail = "Introduzca su correo electrónico"
-enterEmailForMagicLink = "Introduzca su correo electrónico para el enlace mágico"
+enterEmail = "Introduce tu correo electrónico"
+enterEmailForMagicLink = "Introduce tu correo electrónico para el enlace mágico"
enterMfaCode = "Introduce el código de 6 dígitos"
-enterPassword = "Introduzca su contraseña"
+enterPassword = "Introduce tu contraseña"
enterUsername = "Introduce el nombre de usuario"
failedToSignIn = "Error al iniciar sesión con {{provider}}: {{message}}"
forgotPassword = "¿Has olvidado tu contraseña?"
header = "Iniciar sesión"
home = "Inicio"
invalid = "Nombre de usuario o contraseña erróneos."
-locked = "Su cuenta se ha bloqueado."
+locked = "Tu cuenta se ha bloqueado."
loggingIn = "Iniciando sesión..."
logIn = "Iniciar sesión"
login = "Iniciar sesión"
-logoutMessage = "Ha cerrado sesión."
-magicLinkSent = "¡Enlace mágico enviado a {{email}}! Revise su correo electrónico y haga clic en el enlace para iniciar sesión."
-maxUsersReached = "Se alcanzó el número máximo de usuarios para su licencia actual. Póngase en contacto con el administrador para actualizar su plan o añadir más plazas."
+logoutMessage = "Has cerrado sesión."
+magicLinkSent = "¡Enlace mágico enviado a {{email}}! Revisa tu correo electrónico y haz clic en el enlace para iniciar sesión."
+maxUsersReached = "Se alcanzó el número máximo de usuarios para tu licencia actual. Ponte en contacto con el administrador para actualizar tu plan o añadir más plazas."
mfaCode = "Código de autenticación"
mfaPromptBody = "Introduce el código de autenticación de tu aplicación de autenticación para continuar."
mfaPromptTitle = "Autenticación de dos factores"
mfaRequired = "Se requiere código de dos factores"
oauth2AccessDenied = "Acceso denegado"
-oAuth2AdminBlockedUser = "El registro o inicio de sesión de usuarios no registrados está actualmente bloqueado. Por favor, póngase en contacto con el administrador."
+oAuth2AdminBlockedUser = "El registro o inicio de sesión de usuarios no registrados está actualmente bloqueado. Por favor, ponte en contacto con el administrador."
oAuth2AutoCreateDisabled = "Usuario de creación automática de OAUTH2 DESACTIVADO"
oauth2InvalidIdToken = "Token de identificación no válido"
oauth2invalidRequest = "Solicitud no válida"
oauth2InvalidTokenResponse = "Respuesta de token no válida"
oauth2InvalidUserInfoResponse = "Respuesta de información de usuario no válida"
oauth2RequestNotFound = "Solicitud de autorización no encontrada"
-oAuth2RequiresLicense = "El inicio de sesión OAuth/SSO requiere una licencia de pago (Server o Enterprise). Póngase en contacto con el administrador para actualizar su plan."
+oAuth2RequiresLicense = "El inicio de sesión OAuth/SSO requiere una licencia de pago (Server o Enterprise). Ponte en contacto con el administrador para actualizar tu plan."
or = "O"
password = "Contraseña"
passwordChangedSuccess = "¡Contraseña cambiada correctamente! Inicia sesión con tu nueva contraseña."
-passwordResetSent = "¡Enlace de restablecimiento de contraseña enviado a {{email}}! Revise su correo electrónico y siga las instrucciones."
+passwordResetSent = "¡Enlace de restablecimiento de contraseña enviado a {{email}}! Revisa tu correo electrónico y sigue las instrucciones."
passwordUpdatedSuccess = "Tu contraseña se ha actualizado correctamente."
-pleaseEnterBoth = "Por favor, introduzca tanto el correo electrónico como la contraseña"
-pleaseEnterEmail = "Por favor, introduzca su dirección de correo electrónico"
+pleaseEnterBoth = "Por favor, introduce tanto el correo electrónico como la contraseña"
+pleaseEnterEmail = "Por favor, introduce tu dirección de correo electrónico"
relyingPartyRegistrationNotFound = "No hay registro de terceros confiables"
rememberme = "Recordarme"
resetHelp = "Introduce tu correo electrónico para recibir un enlace seguro para restablecer tu contraseña. Si el enlace ha caducado, por favor solicita uno nuevo."
resetYourPassword = "Restablecer tu contraseña"
-saml2RequiresLicense = "El inicio de sesión SAML requiere una licencia de pago (Server o Enterprise). Póngase en contacto con el administrador para actualizar su plan."
+saml2RequiresLicense = "El inicio de sesión SAML requiere una licencia de pago (Server o Enterprise). Ponte en contacto con el administrador para actualizar tu plan."
sending = "Enviando…"
sendMagicLink = "Enviar Enlace Mágico"
sendResetLink = "Enviar enlace de restablecimiento"
@@ -4598,22 +4607,22 @@ sessionExpired = "Tu sesión ha expirado. Vuelve a iniciar sesión."
signin = "Iniciar sesión"
signInAnonymously = "Registrarse como Invitado"
signingIn = "Iniciando sesión..."
-signinTitle = "Por favor, inicie sesión"
+signinTitle = "Por favor, inicia sesión"
signInWith = "Iniciar sesión con"
signOut = "Cerrar Sesión"
ssoSignIn = "Iniciar sesión a través del inicio de sesión único"
subtitle = "Vuelve a iniciar sesión en Stirling PDF"
title = "Iniciar sesión"
-toManySessions = "Tiene demasiadas sesiones activas"
+toManySessions = "Tienes demasiadas sesiones activas"
unexpectedError = "Error inesperado: {{message}}"
updatePassword = "Actualizar contraseña"
useEmailInstead = "Iniciar sesión con correo electrónico"
useMagicLink = "Usar enlace mágico en su lugar"
-userIsDisabled = "El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, póngase en contacto con el administrador."
+userIsDisabled = "El usuario está desactivado, actualmente el acceso está bloqueado para ese nombre de usuario. Por favor, ponte en contacto con el administrador."
username = "Nombre de usuario"
verifyingMfa = "Verificando..."
verifyMfa = "Verificar código"
-youAreLoggedIn = "¡Ha iniciado sesión!"
+youAreLoggedIn = "¡Has iniciado sesión!"
[login.slides.edit]
alt = "Editar PDFs"
@@ -5274,7 +5283,7 @@ bullet1 = "all → selecciona todas las páginas"
title = "Palabras clave especiales"
[pageSelection.tooltip.syntax]
-text = "Use números, rangos, palabras clave y progresiones (n comienza en 0). Se admiten paréntesis."
+text = "Usa números, rangos, palabras clave y progresiones (n comienza en 0). Se admiten paréntesis."
title = "Conceptos Básicos de Sintaxis"
[pageSelection.tooltip.syntax.bullets]
@@ -5383,7 +5392,7 @@ title = "Organizador de páginas"
[pdfOrganiser.desc]
BOOKLET_SORT = "Organice páginas para impresión de folletos (última, primera, segunda, penúltima, …)."
-CUSTOM = "Use una secuencia personalizada de números de página o expresiones para definir un nuevo orden."
+CUSTOM = "Usa una secuencia personalizada de números de página o expresiones para definir un nuevo orden."
DUPLEX_SORT = "Intercale frentes y luego reversos como si un escáner dúplex escaneara todos los frentes y luego todos los reversos (1, n, 2, n-1, …)."
DUPLICATE = "Duplique cada página según el recuento de orden personalizado (por ejemplo, 4 duplica cada página 4 veces)."
ODD_EVEN_MERGE = "Combine dos PDF alternando páginas: impares del primero, pares del segundo."
@@ -6365,7 +6374,7 @@ title = "Umbral de Blancura de Píxeles"
[removeBlanks.tooltip.whitePercent]
bullet1 = "Valores más bajos (por ejemplo, 80%) = Más páginas eliminadas"
bullet2 = "Valores más altos (por ejemplo, 95%) = Solo se eliminan páginas muy en blanco"
-bullet3 = "Use valores más altos para documentos con fondos claros"
+bullet3 = "Usa valores más altos para documentos con fondos claros"
text = "Establece el porcentaje mínimo de píxeles blancos requerido para que una página se considere en blanco y se elimine."
title = "Umbral de Porcentaje Blanco"
@@ -6422,7 +6431,7 @@ failed = "Se produjo un error al eliminar páginas."
placeholder = "Seleccione un archivo PDF en la vista principal para comenzar"
[removePages.pageNumbers]
-error = "Formato de número de página no válido. Use números, rangos (1-5) o expresiones matemáticas (2n+1)"
+error = "Formato de número de página no válido. Usa números, rangos (1-5) o expresiones matemáticas (2n+1)"
label = "Páginas a Eliminar"
placeholder = "por ejemplo, 1,3,5-8,10"
@@ -6595,7 +6604,7 @@ failed = "Se produjo un error al rotar el PDF."
title = "Vista Previa de Rotación"
[rotate.tooltip.controls]
-text = "Use los botones de rotación para ajustar la orientación. El botón izquierdo rota en sentido antihorario, el botón derecho rota en sentido horario. Cada clic rota 90 grados."
+text = "Usa los botones de rotación para ajustar la orientación. El botón izquierdo rota en sentido antihorario y el botón derecho en sentido horario. Cada clic rota 90 grados."
title = "Controles"
[rotate.tooltip.description]
@@ -6709,7 +6718,7 @@ problem4 = "Fotos inclinadas no enderezadas → reduzca el Umbral de Ángulo a ~
problem5 = "Cuadros de polvo/ruido → aumente el Área Mínima de Contorno a 1000-2000"
quickFixes = "Soluciones rápidas"
setupTips = "Consejos de configuración"
-tip1 = "Use un fondo plano y claro"
+tip1 = "Usa un fondo plano y claro"
tip2 = "Deje un pequeño espacio (≈1 cm) entre fotos"
tip3 = "Escanee a 300-600 DPI"
tip4 = "Limpie el cristal del escáner"
@@ -6728,6 +6737,8 @@ placeholder = "Ingrese término de búsqueda..."
searching = "Buscando..."
title = "Buscar PDF"
+[selfHosted]
+
[selfHosted.offline]
hideTools = "Ocultar herramientas no disponibles ▴"
messageNoFallback = "Las herramientas no estarán disponibles hasta que tu servidor vuelva a estar en línea."
@@ -7225,7 +7236,7 @@ defaultLabel = "Firma"
defaultTextLabel = "Firma escrita"
delete = "Eliminar"
description = "Reutilice firmas guardadas en cualquier momento."
-emptyDescription = "Dibuje, suba o escriba una firma arriba y luego use \"Guardar en la biblioteca\" para mantener hasta {{max}} favoritas listas para usar."
+emptyDescription = "Dibuja, sube o escribe una firma arriba y luego usa \"Guardar en la biblioteca\" para mantener hasta {{max}} favoritas listas para usar."
emptyTitle = "Aún no hay firmas guardadas"
heading = "Firmas guardadas"
label = "Etiqueta"
@@ -7394,7 +7405,7 @@ tooltip = "Ingrese el número de páginas para cada archivo dividido"
[split.methods.byPageDivider]
desc = "División automática con hojas divisoras"
name = "Divisor de Páginas"
-tooltip = "Use hojas divisoras con código QR entre documentos al escanear"
+tooltip = "Usa hojas divisoras con código QR entre documentos al escanear"
[split.methods.byPages]
desc = "Extraer páginas específicas (1,3,5-10)"
@@ -7458,7 +7469,7 @@ settings = "Configuración"
bullet1 = "Nivel de Marcador: En qué nivel dividir (1=nivel superior)"
bullet2 = "Incluir Metadatos: Preservar propiedades del documento"
bullet3 = "Permitir Duplicados: Manejar nombres de marcadores repetidos"
-text = "Use marcadores de PDF para dividir automáticamente en límites de capítulos. Requiere PDFs con estructura de marcadores."
+text = "Usa marcadores de PDF para dividir automáticamente en límites de capítulos. Requiere PDFs con estructura de marcadores."
title = "Dividir por Capítulos"
[split.tooltip.byCount]
@@ -7495,7 +7506,7 @@ title = "Dividir por separador de páginas"
bullet1 = "Puntos de división únicos: 3,7 (divide después de las páginas 3 y 7)"
bullet2 = "Puntos de división por rango: 3-8 (divide antes de la página 3 y después de la página 8)"
bullet3 = "Mixto: 2,5-10,15 (divide después de la página 2, antes de la página 5, después de la página 10 y después de la página 15)"
-text = "Divida su PDF en números de página específicos. Usar 'n' divide después de la página n. Usar 'n-m' divide antes de la página n y después de la página m."
+text = "Divide tu PDF en números de página específicos. Usa 'n' para dividir después de la página n. Usa 'n-m' para dividir antes de la página n y después de la página m."
title = "Dividir en Números de Página"
[split.tooltip.byPoster]
@@ -7514,8 +7525,8 @@ text = "Divida cada página en una cuadrícula de secciones. Útil para dividir
title = "Dividir por Secciones de Cuadrícula"
[split.tooltip.bySize]
-bullet1 = "Use MB para archivos más grandes (ej. 10MB)"
-bullet2 = "Use KB para archivos más pequeños (ej. 500KB)"
+bullet1 = "Usa MB para archivos más grandes (ej. 10MB)"
+bullet2 = "Usa KB para archivos más pequeños (ej. 500KB)"
bullet3 = "El sistema dividirá en límites de página"
text = "Crear múltiples PDFs que no excedan un tamaño de archivo especificado. Ideal para limitaciones de tamaño de archivo o archivos adjuntos de correo electrónico."
title = "Dividir por Tamaño de Archivo"
@@ -8294,7 +8305,7 @@ title = "Configuración de Apariencia"
title = "Carga de Imagen"
[watermark.tooltip.file.recommendations]
-bullet1 = "Use logos o sellos con fondos transparentes"
+bullet1 = "Usa logos o sellos con fondos transparentes"
bullet2 = "Los diseños simples funcionan mejor que las imágenes complejas"
bullet3 = "Considere el tamaño final del documento al elegir la resolución"
text = "Consejos para obtener resultados óptimos de marcas de agua de imagen."
@@ -8373,7 +8384,7 @@ title = "Selección de Tipo de Marca de Agua"
bullet1 = "Cargue cualquier formato de imagen"
bullet2 = "Mantiene la calidad de la imagen"
bullet3 = "Perfecto para logos y sellos"
-text = "Use logos, sellos o cualquier imagen como marca de agua. Excelente para marca e identificación visual."
+text = "Usa logos, sellos o cualquier imagen como marca de agua. Excelente para marca e identificación visual."
title = "Marcas de Agua de Imagen"
[watermark.tooltip.type.text]
@@ -8595,7 +8606,7 @@ description = "Genera un enlace seguro que permite al usuario establecer su prop
email = "Dirección de correo electrónico"
emailDescription = "Opcional: deja en blanco para un enlace de invitación general que pueda usar cualquiera"
emailFailed = "Enlace de invitación generado, pero falló el correo"
-emailFailedDetails = "Error: {0}. Por favor, comparte el enlace de invitación manualmente."
+emailFailedDetails = "Error: {0}. Por favor, comparta el enlace de invitación manualmente."
emailOptional = "Opcional: deja en blanco para un enlace de invitación general"
emailPlaceholder = "user@example.com (opcional)"
emailRequired = "Se requiere una dirección de correo electrónico"
diff --git a/scripts/ignore_translation.toml b/scripts/ignore_translation.toml
index fef3648fbc..e6fcf48f17 100644
--- a/scripts/ignore_translation.toml
+++ b/scripts/ignore_translation.toml
@@ -178,12 +178,115 @@ ignore = [
[es_ES]
ignore = [
+ 'adjustPageScale.pageSize.legal',
+ 'admin.error',
+ 'admin.settings.advanced.processExecutor.calibre',
+ 'admin.settings.advanced.processExecutor.ghostscript',
+ 'admin.settings.advanced.processExecutor.libreOffice',
+ 'admin.settings.advanced.processExecutor.ocrMyPdf',
+ 'admin.settings.advanced.processExecutor.pythonOpenCv',
+ 'admin.settings.advanced.processExecutor.qpdf',
+ 'admin.settings.advanced.processExecutor.tesseract',
+ 'admin.settings.advanced.processExecutor.weasyPrint',
+ 'admin.settings.connections.mobileScanner.pageFormatA4',
+ 'admin.settings.connections.oauth2.label',
+ 'admin.settings.connections.pageFormatA4',
+ 'admin.settings.connections.saml2.label',
+ 'admin.settings.security.xFrameOptions.label',
+ 'adminUserSettings.roles',
+ 'audit.clearData.irreversible',
+ 'certSign.appearance.invisible',
+ 'certSign.appearance.visible',
+ 'certSign.collab.participant.certInvalid',
+ 'certSign.collab.signRequest.steps.invisible',
+ 'certSign.collab.signRequest.steps.visible',
+ 'certSign.collab.signRequest.text.colorLabel',
+ 'certSign.collab.signRequest.text.fontSizePlaceholder',
+ 'common.error',
+ 'compare.pixel.base',
+ 'config.account.profilePicture.cropper.zoom',
+ 'config.overview.error',
+ 'config.plan',
+ 'convert.cbrToPdf',
+ 'convert.cbzToPdf',
+ 'convert.dpi',
+ 'convert.ebookToPdf',
+ 'convert.markdown',
+ 'convert.pdfToCbr',
+ 'convert.pdfToCbz',
+ 'convert.pdfToEpub',
+ 'convert.pptExt',
+ 'convert.svgToPdf',
+ 'cookieBanner.services.posthog',
+ 'cookieBanner.services.scarf',
+ 'credits.modal.enterpriseSubscription',
+ 'credits.modal.popular',
+ 'credits.modal.teamSubscription',
+ 'defaultApp.error.title',
+ 'endpointStatistics.dataTypeApi',
+ 'endpointStatistics.dataTypeUi',
+ 'endpointStatistics.top10',
+ 'endpointStatistics.top20',
+ 'error._value',
+ 'fileManager.filterLocal',
+ 'fileManager.googleDrive',
+ 'fileManager.googleDriveShort',
+ 'firstLogin.error',
+ 'footer.discord',
+ 'footer.issues',
+ 'getPdfInfo.other.javaScript',
+ 'getPdfInfo.perPage.multimedia',
+ 'getPdfInfo.summary.tech.javaScript',
+ 'getPdfInfo.summary.tech.multimedia',
+ 'home.devApi.title',
+ 'landing.heroTitle',
'language.direction',
+ 'mobileScanner.flash',
'no',
+ 'pdfTextEditor.groupingMode.auto',
+ 'plan.enterprise.name',
+ 'plan.popular',
+ 'plan.pro.name',
+ 'plan.team.name',
+ 'plan.trial.message',
'pro',
+ 'provider.googledrive.name',
+ 'provider.oauth2.generic.scope',
+ 'provider.oauth2.keycloak.scope',
+ 'provider.saml2.name',
+ 'provider.saml2.scope',
+ 'quickAccess.accessRoleEditor',
+ 'redact.manual.zoom',
+ 'redact.modeSelector.manual',
+ 'removeBlanks.whitePercent.unit',
+ 'settings.configuration.endpoints',
+ 'settings.connection.mode.saas',
+ 'settings.general.title',
+ 'settings.licensingAnalytics.plan',
+ 'settings.planBilling.tier.enterprise',
+ 'settings.planBilling.tier.team',
+ 'settings.planBilling.tier.teamBadge',
+ 'settings.policiesPrivacy.legal',
+ 'settings.securityAuth.telegram',
+ 'setup.mode.saas.title',
+ 'setup.server.type.saas',
'showJS.tags',
+ 'storageShare.roleEditor',
+ 'team.personal',
+ 'toolPicker.subcategories.general',
'update.priority.normal',
+ 'usage.controls.dataType.api',
+ 'usage.controls.dataType.ui',
+ 'usage.controls.top10',
+ 'usage.controls.top20',
+ 'usage.showing.top10',
+ 'usage.showing.top20',
'validateSignature.cert.bits',
+ 'validateSignature.downloadType.csv',
+ 'validateSignature.downloadType.json',
+ 'validateSignature.downloadType.pdf',
+ 'workspace.people.authType.oauth',
+ 'workspace.people.authType.saml',
]
[eu_ES]