From 166f6d2d980b2dbf0e846186db64e37ee8bdd83f Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 24 Sep 2025 20:37:51 +0100 Subject: [PATCH 1/2] path (#4488) # Description of Changes --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- frontend/index.html | 1 + .../src/components/shared/LandingPage.tsx | 5 +++-- frontend/src/components/shared/Tooltip.tsx | 3 ++- frontend/src/constants/app.ts | 16 ++++++++++++++++ frontend/src/hooks/useCookieConsent.ts | 7 ++++--- frontend/src/hooks/useUrlSync.ts | 4 +++- frontend/src/i18n.ts | 4 +++- frontend/src/index.tsx | 3 ++- frontend/src/tools/SwaggerUI.tsx | 5 +++-- frontend/src/utils/urlRouting.ts | 19 +++++++++++++------ frontend/vite.config.ts | 2 +- 11 files changed, 51 insertions(+), 18 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 31f1b3008..b563bdcd8 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -2,6 +2,7 @@ + diff --git a/frontend/src/components/shared/LandingPage.tsx b/frontend/src/components/shared/LandingPage.tsx index a3ea42fab..787b0e565 100644 --- a/frontend/src/components/shared/LandingPage.tsx +++ b/frontend/src/components/shared/LandingPage.tsx @@ -5,6 +5,7 @@ import LocalIcon from './LocalIcon'; import { useTranslation } from 'react-i18next'; import { useFileHandler } from '../../hooks/useFileHandler'; import { useFilesModalContext } from '../../contexts/FilesModalContext'; +import { BASE_PATH } from '../../constants/app'; const LandingPage = () => { const { addFiles } = useFileHandler(); @@ -72,7 +73,7 @@ const LandingPage = () => { }} > Stirling PDF Logo { {/* Stirling PDF Branding */} Stirling PDF diff --git a/frontend/src/components/shared/Tooltip.tsx b/frontend/src/components/shared/Tooltip.tsx index 160857a50..543422609 100644 --- a/frontend/src/components/shared/Tooltip.tsx +++ b/frontend/src/components/shared/Tooltip.tsx @@ -6,6 +6,7 @@ import { useTooltipPosition } from '../../hooks/useTooltipPosition'; import { TooltipTip } from '../../types/tips'; import { TooltipContent } from './tooltip/TooltipContent'; import { useSidebarContext } from '../../contexts/SidebarContext'; +import { BASE_PATH } from '../../constants/app'; import styles from './tooltip/Tooltip.module.css'; export interface TooltipProps { @@ -328,7 +329,7 @@ export const Tooltip: React.FC = ({
{header.logo || ( Stirling PDF diff --git a/frontend/src/constants/app.ts b/frontend/src/constants/app.ts index 3b8b765df..c83cffcfd 100644 --- a/frontend/src/constants/app.ts +++ b/frontend/src/constants/app.ts @@ -5,3 +5,19 @@ export const getBaseUrl = (): string => { const { config } = useAppConfig(); return config?.baseUrl || 'https://stirling.com'; }; + +// Base path from Vite config - build-time constant, normalized (no trailing slash) +// When no subpath, use empty string instead of '.' to avoid relative path issues +export const BASE_PATH = (import.meta.env.BASE_URL || '/').replace(/\/$/, '').replace(/^\.$/, ''); + +/** For in-app navigations when you must touch window.location (rare). */ +export const withBasePath = (path: string): string => { + const clean = path.startsWith('/') ? path : `/${path}`; + return `${BASE_PATH}${clean}`; +}; + +/** For OAuth (needs absolute URL with scheme+host) */ +export const absoluteWithBasePath = (path: string): string => { + const clean = path.startsWith('/') ? path : `/${path}`; + return `${window.location.origin}${BASE_PATH}${clean}`; +}; diff --git a/frontend/src/hooks/useCookieConsent.ts b/frontend/src/hooks/useCookieConsent.ts index dd00f4396..1dd324ce7 100644 --- a/frontend/src/hooks/useCookieConsent.ts +++ b/frontend/src/hooks/useCookieConsent.ts @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { BASE_PATH } from '../constants/app'; declare global { interface Window { @@ -37,17 +38,17 @@ export const useCookieConsent = ({ analyticsEnabled = false }: CookieConsentConf // Load the cookie consent CSS files first const mainCSS = document.createElement('link'); mainCSS.rel = 'stylesheet'; - mainCSS.href = '/css/cookieconsent.css'; + mainCSS.href = `${BASE_PATH}/css/cookieconsent.css`; document.head.appendChild(mainCSS); const customCSS = document.createElement('link'); customCSS.rel = 'stylesheet'; - customCSS.href = '/css/cookieconsentCustomisation.css'; + customCSS.href = `${BASE_PATH}/css/cookieconsentCustomisation.css`; document.head.appendChild(customCSS); // Load the cookie consent library const script = document.createElement('script'); - script.src = '/js/thirdParty/cookieconsent.umd.js'; + script.src = `${BASE_PATH}/js/thirdParty/cookieconsent.umd.js`; script.onload = () => { // Small delay to ensure DOM is ready setTimeout(() => { diff --git a/frontend/src/hooks/useUrlSync.ts b/frontend/src/hooks/useUrlSync.ts index e65e5500f..a90ee4fbe 100644 --- a/frontend/src/hooks/useUrlSync.ts +++ b/frontend/src/hooks/useUrlSync.ts @@ -7,6 +7,7 @@ import { ToolId } from '../types/toolId'; import { parseToolRoute, updateToolRoute, clearToolRoute } from '../utils/urlRouting'; import { ToolRegistry } from '../data/toolsTaxonomy'; import { firePixel } from '../utils/scarfTracking'; +import { withBasePath } from '../constants/app'; /** * Hook to sync workbench and tool with URL using registry @@ -51,7 +52,8 @@ export function useNavigationUrlSync( } else if (prevSelectedTool.current !== null) { // Only clear URL if we had a tool before (user navigated away) // Don't clear on initial load when both current and previous are null - if (window.location.pathname !== '/') { + const homePath = withBasePath('/'); + if (window.location.pathname !== homePath) { clearToolRoute(false); // Use pushState for user navigation } } diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index b0ce8fdf7..f57d4b1e3 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -74,7 +74,9 @@ i18n loadPath: (lngs: string[], namespaces: string[]) => { // Map 'en' to 'en-GB' for loading translations const lng = lngs[0] === 'en' ? 'en-GB' : lngs[0]; - return `/locales/${lng}/${namespaces[0]}.json`; + const basePath = import.meta.env.BASE_URL || '/'; + const cleanBasePath = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath; + return `${cleanBasePath}/locales/${lng}/${namespaces[0]}.json`; }, }, diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx index 1976eb49d..431aa7bf3 100644 --- a/frontend/src/index.tsx +++ b/frontend/src/index.tsx @@ -10,6 +10,7 @@ import App from './App'; import './i18n'; // Initialize i18next import posthog from 'posthog-js'; import { PostHogProvider } from 'posthog-js/react'; +import { BASE_PATH } from './constants/app'; // Compute initial color scheme function getInitialScheme(): 'light' | 'dark' { @@ -60,7 +61,7 @@ root.render( - + diff --git a/frontend/src/tools/SwaggerUI.tsx b/frontend/src/tools/SwaggerUI.tsx index 20d67f496..32034def3 100644 --- a/frontend/src/tools/SwaggerUI.tsx +++ b/frontend/src/tools/SwaggerUI.tsx @@ -1,10 +1,11 @@ import React, { useEffect } from "react"; import { BaseToolProps } from "../types/tool"; +import { withBasePath } from "../constants/app"; const SwaggerUI: React.FC = () => { useEffect(() => { // Redirect to Swagger UI - window.open("/swagger-ui/5.21.0/index.html", "_blank"); + window.open(withBasePath("/swagger-ui/5.21.0/index.html"), "_blank"); }, []); return ( @@ -12,7 +13,7 @@ const SwaggerUI: React.FC = () => {

Opening Swagger UI in a new tab...

If it didn't open automatically,{" "} - + click here

diff --git a/frontend/src/utils/urlRouting.ts b/frontend/src/utils/urlRouting.ts index 3ca35e9a7..d14ca923a 100644 --- a/frontend/src/utils/urlRouting.ts +++ b/frontend/src/utils/urlRouting.ts @@ -8,12 +8,17 @@ import { getDefaultWorkbench } from '../types/workbench'; import { ToolRegistry, getToolWorkbench, getToolUrlPath } from '../data/toolsTaxonomy'; import { firePixel } from './scarfTracking'; import { URL_TO_TOOL_MAP } from './urlMapping'; +import { BASE_PATH, withBasePath } from '../constants/app'; /** * Parse the current URL to extract tool routing information */ export function parseToolRoute(registry: ToolRegistry): ToolRoute { - const path = window.location.pathname; + const fullPath = window.location.pathname; + // Remove base path to get app-relative path + const path = BASE_PATH && fullPath.startsWith(BASE_PATH) + ? fullPath.slice(BASE_PATH.length) || '/' + : fullPath; const searchParams = new URLSearchParams(window.location.search); // First, check URL mapping for multiple URL aliases @@ -83,7 +88,8 @@ export function updateToolRoute(toolId: ToolId, registry: ToolRegistry, replace: return; } - const newPath = getToolUrlPath(toolId, tool); + const toolPath = getToolUrlPath(toolId, tool); + const newPath = withBasePath(toolPath); const searchParams = new URLSearchParams(window.location.search); // Remove tool query parameter since we're using path-based routing @@ -99,7 +105,7 @@ export function clearToolRoute(replace: boolean = false): void { const searchParams = new URLSearchParams(window.location.search); searchParams.delete('tool'); - updateUrl('/', searchParams, replace); + updateUrl(withBasePath('/'), searchParams, replace); } /** @@ -117,11 +123,12 @@ export function generateShareableUrl(toolId: ToolId | null, registry: ToolRegist const baseUrl = window.location.origin; if (!toolId || !registry[toolId]) { - return baseUrl; + return `${baseUrl}${BASE_PATH || ''}`; } const tool = registry[toolId]; - const path = getToolUrlPath(toolId, tool); - return `${baseUrl}${path}`; + const toolPath = getToolUrlPath(toolId, tool); + const fullPath = withBasePath(toolPath); + return `${baseUrl}${fullPath}`; } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 1db9de625..59ebfd663 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -12,5 +12,5 @@ export default defineConfig({ }, }, }, - base: "./", + base: process.env.RUN_SUBPATH ? `/${process.env.RUN_SUBPATH}` : './', }); From 1219cebd075cae2f00a4f8990c1e405153978021 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Thu, 25 Sep 2025 12:50:19 +0100 Subject: [PATCH 2/2] Language stuff (#4490) # Description of Changes --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --- .gitignore | 7 + .../public/locales/de-DE/translation.json | 2163 ++++++++++++++--- .../public/locales/it-IT/translation.json | 2162 +++++++++++++--- .../public/locales/zh-CN/translation.json | 2156 +++++++++++++--- scripts/translations/README.md | 403 +++ scripts/translations/ai_translation_helper.py | 408 ++++ scripts/translations/compact_translator.py | 177 ++ scripts/translations/json_beautifier.py | 262 ++ scripts/translations/translation_analyzer.py | 314 +++ scripts/translations/translation_merger.py | 371 +++ 10 files changed, 7489 insertions(+), 934 deletions(-) create mode 100644 scripts/translations/README.md create mode 100644 scripts/translations/ai_translation_helper.py create mode 100644 scripts/translations/compact_translator.py create mode 100644 scripts/translations/json_beautifier.py create mode 100644 scripts/translations/translation_analyzer.py create mode 100644 scripts/translations/translation_merger.py diff --git a/.gitignore b/.gitignore index 37df23f58..b339d7ff6 100644 --- a/.gitignore +++ b/.gitignore @@ -203,3 +203,10 @@ id_ed25519.pub # node_modules node_modules/ + +# Translation temp files +*_compact.json +*compact*.json +test_batch.json +*.backup.*.json +frontend/public/locales/*/translation.backup*.json diff --git a/frontend/public/locales/de-DE/translation.json b/frontend/public/locales/de-DE/translation.json index 65c92fbe0..2066c7699 100644 --- a/frontend/public/locales/de-DE/translation.json +++ b/frontend/public/locales/de-DE/translation.json @@ -21,7 +21,7 @@ "submit": "Seitenzahlen hinzufügen" }, "pdfPrompt": "PDF(s) auswählen", - "multiPdfPrompt": "PDFs auswählen(2+)", + "multiPdfPrompt": "PDFs auswählen (2+)", "multiPdfDropPrompt": "Wählen Sie alle gewünschten PDFs aus (oder ziehen Sie sie per Drag & Drop hierhin)", "imgPrompt": "Wählen Sie ein Bild", "genericSubmit": "Absenden", @@ -31,14 +31,32 @@ "processTimeWarning": "Achtung: Abhängig von der Dateigröße kann dieser Prozess bis zu einer Minute dauern", "pageOrderPrompt": "Seitenreihenfolge (Geben Sie eine durch Komma getrennte Liste von Seitenzahlen ein):", "pageSelectionPrompt": "Benutzerdefinierte Seitenauswahl (Geben Sie eine durch Kommas getrennte Liste von Seitenzahlen 1,5,6 oder Funktionen wie 2n+1 ein):", - "goToPage": "Los", + "goToPage": "Gehe zu", "true": "Wahr", "false": "Falsch", "unknown": "Unbekannt", + "app": { + "description": "Die kostenlose Adobe Acrobat Alternative (über 10 Millionen Downloads)" + }, "save": "Speichern", "saveToBrowser": "Im Browser speichern", + "download": "Herunterladen", + "undoOperationTooltip": "Klicken zum Rückgängigmachen der letzten Operation und Wiederherstellen der ursprünglichen Dateien", + "undo": "Rückgängig", + "moreOptions": "Weitere Optionen", + "editYourNewFiles": "Ihre neue(n) Datei(en) bearbeiten", "close": "Schließen", + "fileSelected": "Ausgewählt: {{filename}}", + "chooseFile": "Datei wählen", "filesSelected": "Dateien ausgewählt", + "files": { + "title": "Dateien", + "upload": "Hochladen", + "uploadFiles": "Dateien hochladen", + "addFiles": "Dateien hinzufügen", + "selectFromWorkbench": "Dateien von der Workbench auswählen oder", + "selectMultipleFromWorkbench": "Mindestens {{count}} Dateien von der Workbench auswählen oder" + }, "noFavourites": "Keine Favoriten hinzugefügt", "downloadComplete": "Download abgeschlossen", "bored": "Langeweile beim Warten?", @@ -52,14 +70,14 @@ "small": "Klein", "medium": "Mittel", "large": "Groß", - "x-large": "Extra Groß" + "x-large": "Extra groß" }, "error": { "pdfPassword": "Das PDF-Dokument ist passwortgeschützt und das Passwort wurde entweder nicht angegeben oder war falsch", "_value": "Fehler", "sorry": "Entschuldigung für das Problem!", - "needHelp": "Brauchst du Hilfe / Ein Problem gefunden?", - "contactTip": "Wenn du weiterhin Probleme hast, zögere nicht, uns um Hilfe zu bitten. Du kannst ein Ticket auf unserer GitHub-Seite einreichen oder uns über Discord kontaktieren:", + "needHelp": "Brauchen Sie Hilfe / Ein Problem gefunden?", + "contactTip": "Wenn Sie weiterhin Probleme haben, zögern Sie nicht, uns um Hilfe zu bitten. Du kannst ein Ticket auf unserer GitHub-Seite einreichen oder uns über Discord kontaktieren:", "404": { "head": "404 - Seite nicht gefunden | Ups, wir sind im Code gestolpert!", "1": "Wir können die gesuchte Seite nicht finden.", @@ -71,6 +89,10 @@ "githubSubmit": "GitHub - Ein Ticket einreichen", "discordSubmit": "Discord - Unterstützungsbeitrag einreichen" }, + "warning": { + "tooltipTitle": "Warnung" + }, + "edit": "Bearbeiten", "delete": "Löschen", "username": "Benutzername", "password": "Passwort", @@ -82,8 +104,9 @@ "green": "Grün", "blue": "Blau", "custom": "benutzerdefiniert...", + "comingSoon": "Demnächst verfügbar", "WorkInProgess": "In Arbeit, funktioniert möglicherweise nicht oder ist fehlerhaft. Bitte melden Sie alle Probleme!", - "poweredBy": "Unterstützt von", + "poweredBy": "Bereitgestellt von", "yes": "Ja", "no": "Nein", "changedCredsMessage": "Anmeldedaten geändert!", @@ -115,12 +138,14 @@ "page": "Seite", "pages": "Seiten", "loading": "Laden...", + "review": "Überprüfen", "addToDoc": "In Dokument hinzufügen", "reset": "Zurücksetzen", "apply": "Anwenden", "noFileSelected": "Keine Datei ausgewählt. Bitte laden Sie eine hoch.", "legal": { "privacy": "Datenschutz", + "iAgreeToThe": "Ich stimme allen folgenden zu:", "terms": "AGB", "accessibility": "Barrierefreiheit", "cookie": "Cookie-Richtlinie", @@ -248,7 +273,7 @@ "usernameInfo": "Der Benutzername darf nur Buchstaben, Zahlen und die folgenden Sonderzeichen @._+- enthalten oder muss eine gültige E-Mail-Adresse sein.", "roles": "Rollen", "role": "Rolle", - "actions": "Aktions", + "actions": "Aktionen", "apiUser": "Eingeschränkter API-Benutzer", "extraApiUser": "Zusätzlicher eingeschränkter API-Benutzer", "webOnlyUser": "Nur Web-Benutzer", @@ -317,7 +342,7 @@ "refreshPage": "Seite aktualisieren" }, "home": { - "desc": "Ihr lokal gehosteter One-Stop-Shop für alle Ihre PDF-Anforderungen.", + "desc": "Ihre lokal gehostete All-in-one-Lösung für alle Ihre PDF-Anforderungen.", "searchBar": "Suche nach Funktionen...", "viewPdf": { "title": "PDF anzeigen/bearbeiten", @@ -347,13 +372,9 @@ "title": "Drehen", "desc": "Drehen Sie Ihre PDFs ganz einfach" }, - "imageToPDF": { - "title": "Bild zu PDF", - "desc": "Konvertieren Sie ein Bild (PNG, JPEG, GIF) in ein PDF" - }, - "pdfToImage": { - "title": "PDF zu Bild", - "desc": "Konvertieren Sie ein PDF in ein Bild (PNG, JPEG, GIF)" + "convert": { + "title": "Umwandeln", + "desc": "Dateien zwischen verschiedenen Formaten konvertieren" }, "pdfOrganiser": { "title": "Organisieren", @@ -363,22 +384,14 @@ "title": "Bild einfügen", "desc": "Fügt ein Bild an eine bestimmte Stelle im PDF ein (in Arbeit)" }, + "addAttachments": { + "title": "Anhänge hinzufügen", + "desc": "Eingebettete Dateien (Anhänge) zu einer PDF hinzufügen oder entfernen" + }, "watermark": { "title": "Wasserzeichen hinzufügen", "desc": "Fügen Sie ein eigenes Wasserzeichen zu Ihrem PDF hinzu" }, - "permissions": { - "title": "Berechtigungen ändern", - "desc": "Die Berechtigungen für Ihr PDF-Dokument verändern" - }, - "pageRemover": { - "title": "Entfernen", - "desc": "Ungewollte Seiten aus dem PDF entfernen" - }, - "addPassword": { - "title": "Passwort hinzufügen", - "desc": "Das PDF mit einem Passwort verschlüsseln" - }, "removePassword": { "title": "Passwort entfernen", "desc": "Den Passwortschutz eines PDFs entfernen" @@ -395,10 +408,6 @@ "title": "Metadaten ändern", "desc": "Ändern/Entfernen/Hinzufügen von Metadaten aus einem PDF-Dokument" }, - "fileToPDF": { - "title": "Datei in PDF konvertieren", - "desc": "Konvertieren Sie nahezu jede Datei in PDF (DOCX, PNG, XLS, PPT, TXT und mehr)" - }, "ocr": { "title": "Führe OCR/Cleanup-Scans aus", "desc": "Cleanup scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu" @@ -407,33 +416,9 @@ "title": "Bilder extrahieren", "desc": "Extrahiert alle Bilder aus einer PDF-Datei und speichert sie als Zip-Archiv" }, - "pdfToPDFA": { - "title": "PDF zu PDF/A konvertieren", - "desc": "PDF zu PDF/A für Langzeitarchivierung konvertieren" - }, - "PDFToWord": { - "title": "PDF zu Word", - "desc": "PDF in Word-Formate konvertieren (DOC, DOCX und ODT)" - }, - "PDFToPresentation": { - "title": "PDF zu Präsentation", - "desc": "PDF in Präsentationsformate konvertieren (PPT, PPTX und ODP)" - }, - "PDFToText": { - "title": "PDF in Text/RTF", - "desc": "PDF in Text- oder RTF-Format konvertieren" - }, - "PDFToHTML": { - "title": "PDF in HTML", - "desc": "PDF in HTML-Format konvertieren" - }, - "PDFToXML": { - "title": "PDF in XML", - "desc": "PDF in XML-Format konvertieren" - }, - "ScannerImageSplit": { + "scannerImageSplit": { "title": "Gescannte Fotos erkennen/aufteilen", - "desc": "Teilt mehrere Fotos innerhalb eines Fotos/PDF" + "desc": "Teilt mehrere Fotos aus einem Foto/PDF auf" }, "sign": { "title": "Signieren", @@ -443,6 +428,10 @@ "title": "Abflachen", "desc": "Alle interaktiven Elemente und Formulare aus einem PDF entfernen" }, + "certSign": { + "title": "Mit Zertifikat signieren", + "desc": "Ein PDF mit einem Zertifikat/Schlüssel (PEM/P12) signieren" + }, "repair": { "title": "Reparatur", "desc": "Versucht, ein beschädigtes/kaputtes PDF zu reparieren" @@ -459,10 +448,6 @@ "title": "Vergleichen", "desc": "Vergleicht und zeigt die Unterschiede zwischen zwei PDF-Dokumenten an" }, - "certSign": { - "title": "Mit Zertifikat signieren", - "desc": "Ein PDF mit einem Zertifikat/Schlüssel (PEM/P12) signieren" - }, "removeCertSign": { "title": "Zertifikatsignatur entfernen", "desc": "Zertifikatsignatur aus PDF entfernen" @@ -471,21 +456,21 @@ "title": "Mehrseitiges Layout", "desc": "Mehrere Seiten eines PDF zu einer Seite zusammenführen" }, + "bookletImposition": { + "title": "Broschüren-Layout", + "desc": "Broschüren mit korrekter Seitenreihenfolge und mehrseitigem Layout für Druck und Bindung erstellen" + }, "scalePages": { "title": "Seitengröße/Skalierung anpassen", "desc": "Größe/Skalierung der Seite und/oder des Inhalts ändern" }, - "pipeline": { - "title": "Pipeline", - "desc": "Mehrere Aktionen auf ein PDF anwenden, definiert durch ein Pipeline Skript" - }, "addPageNumbers": { "title": "Seitenzahlen hinzufügen", "desc": "Hinzufügen von Seitenzahlen an einer bestimmten Stelle" }, - "auto-rename": { - "title": "PDF automatisch umbenennen", - "desc": "PDF-Datei anhand von erkannten Kopfzeilen umbenennen" + "autoRename": { + "title": "PDF-Datei automatisch umbenennen", + "desc": "Benennt eine PDF-Datei automatisch basierend auf der erkannten Überschrift um" }, "adjustContrast": { "title": "Farben/Kontrast anpassen", @@ -499,34 +484,14 @@ "title": "PDF automatisch teilen", "desc": "Physisch gescannte PDF anhand von Splitter-Seiten und QR-Codes aufteilen" }, - "sanitizePDF": { - "title": "PDF Bereinigen", - "desc": "Entfernen von Skripten und anderen Elementen aus PDF-Dateien" - }, - "URLToPDF": { - "title": "URL/Website zu PDF", - "desc": "Konvertiert jede http(s)URL zu PDF" - }, - "HTMLToPDF": { - "title": "HTML zu PDF", - "desc": "Konvertiert jede HTML-Datei oder Zip-Archiv zu PDF" - }, - "MarkdownToPDF": { - "title": "Markdown zu PDF", - "desc": "Konvertiert jede Markdown-Datei zu PDF" - }, - "PDFToMarkdown": { - "title": "PDF zu Markdown", - "desc": "Konvertiert jedes PDF in Markdown" + "sanitize": { + "title": "Bereinigen", + "desc": "Potentiell schädliche Elemente aus PDF-Dateien entfernen" }, "getPdfInfo": { "title": "Alle Informationen anzeigen", "desc": "Erfasst alle möglichen Informationen in einer PDF" }, - "pageExtracter": { - "title": "Seite(n) extrahieren", - "desc": "Extrahiert ausgewählte Seiten aus einer PDF" - }, "pdfToSinglePage": { "title": "PDF zu einer Seite zusammenfassen", "desc": "Fügt alle PDF-Seiten zu einer einzigen großen Seite zusammen" @@ -535,33 +500,21 @@ "title": "Javascript anzeigen", "desc": "Alle Javascript Funktionen in einer PDF anzeigen" }, - "autoRedact": { - "title": "Automatisch zensieren/schwärzen", - "desc": "Automatisches Zensieren (Schwärzen) von Text in einer PDF-Datei basierend auf dem eingegebenen Text" - }, "redact": { "title": "Manuell zensieren/schwärzen", "desc": "Zensiere (Schwärze) eine PDF-Datei durch Auswählen von Text, gezeichneten Formen und/oder ausgewählten Seite(n)" }, - "PDFToCSV": { - "title": "Tabelle extrahieren", - "desc": "Tabelle aus PDF in CSV extrahieren" + "overlayPdfs": { + "title": "PDFs überlagern", + "desc": "PDFs über eine andere PDF überlagern" }, - "split-by-size-or-count": { - "title": "Teilen nach Größe/Anzahl", - "desc": "Teilen Sie ein einzelnes PDF basierend auf Größe, Seitenanzahl oder Dokumentanzahl in mehrere Dokumente auf" + "splitBySections": { + "title": "PDF nach Abschnitten aufteilen", + "desc": "Jede Seite einer PDF in kleinere horizontale und vertikale Abschnitte unterteilen" }, - "overlay-pdfs": { - "title": "PDF mit Overlay versehen", - "desc": "Überlagert eine PDF über eine andere PDF" - }, - "split-by-sections": { - "title": "PDF in Abschnitte teilen", - "desc": "Teilen Sie jede Seite einer PDF-Datei in kleinere horizontale und vertikale Abschnitte auf" - }, - "AddStampRequest": { + "addStamp": { "title": "Stempel zu PDF hinzufügen", - "desc": "Fügen Sie an festgelegten Stellen Text oder Bildstempel hinzu" + "desc": "Text- oder Bildstempel an festgelegten Positionen hinzufügen" }, "removeImage": { "title": "Bild entfernen", @@ -575,48 +528,78 @@ "title": "PDF-Signatur überprüfen", "desc": "Digitale Signaturen und Zertifikate in PDF-Dokumenten überprüfen" }, - "replace-color": { - "title": "Farbe ersetzen und invertieren", - "desc": "Ersetzen Sie die Farbe des Texts und Hintergrund der PDF-Datei und invertieren Sie die komplette Farbe der PDF-Datei, um die Dateigröße zu reduzieren" + "swagger": { + "title": "API-Dokumentation", + "desc": "API-Dokumentation anzeigen und Endpunkte testen" }, - "convert": { - "title": "Umwandeln" - }, - "attachments": { - "title": "Anhänge hinzufügen", - "desc": "Eingebettete Dateien (Anhänge) zu einem PDF hinzufügen oder daraus entfernen" + "fakeScan": { + "title": "Scan simulieren", + "desc": "Eine PDF erstellen, die wie gescannt aussieht" }, "editTableOfContents": { "title": "Inhaltsverzeichnis bearbeiten", "desc": "Hinzufügen oder Bearbeiten von Lesezeichen und Inhaltsverzeichnissen in PDF-Dokumenten" }, + "manageCertificates": { + "title": "Zertifikate verwalten", + "desc": "Digitale Zertifikatsdateien für die PDF-Signierung importieren, exportieren oder löschen." + }, + "read": { + "title": "Lesen", + "desc": "PDFs anzeigen und kommentieren. Text hervorheben, zeichnen oder Kommentare für Überprüfung und Zusammenarbeit einfügen." + }, + "reorganizePages": { + "title": "Seiten neu anordnen", + "desc": "PDF-Seiten mit visueller Drag-and-Drop-Steuerung neu anordnen, duplizieren oder löschen." + }, "extractPages": { - "title": "Seiten extrahieren" + "title": "Seiten extrahieren", + "desc": "Spezifische Seiten aus einem PDF-Dokument extrahieren" }, "removePages": { "title": "Entfernen", "desc": "Ungewollte Seiten aus dem PDF entfernen" }, - "removeImagePdf": { - "title": "Bild entfernen", - "desc": "Bild aus PDF entfernen, um die Dateigröße zu verringern" - }, "autoSizeSplitPDF": { "title": "Teilen nach Größe/Anzahl", "desc": "Teilen Sie ein einzelnes PDF basierend auf Größe, Seitenanzahl oder Dokumentanzahl in mehrere Dokumente auf" }, - "adjust-contrast": { - "title": "Farben/Kontrast anpassen", - "desc": "Kontrast, Sättigung und Helligkeit einer PDF anpassen" - }, "replaceColorPdf": { "title": "Farbe ersetzen und invertieren", "desc": "Ersetzen Sie die Farbe des Texts und Hintergrund der PDF-Datei und invertieren Sie die komplette Farbe der PDF-Datei, um die Dateigröße zu reduzieren" }, + "devApi": { + "desc": "Link zur API-Dokumentation" + }, + "devFolderScanning": { + "title": "Automatische Ordnerüberwachung", + "desc": "Link zum Leitfaden für automatisches Ordner-Scannen" + }, + "devSsoGuide": { + "title": "SSO-Anleitung", + "desc": "Link zum SSO-Leitfaden" + }, + "devAirgapped": { + "title": "Offline-Installation", + "desc": "Link zum Air-Gap-Einrichtungsleitfaden" + }, + "addPassword": { + "title": "Passwort hinzufügen", + "desc": "Das PDF mit einem Passwort verschlüsseln" + }, "changePermissions": { - "title": "Berechtigungen ändern" + "title": "Berechtigungen ändern", + "desc": "Dokumentbeschränkungen und -berechtigungen ändern" + }, + "automate": { + "title": "Automatisieren", + "desc": "Mehrstufige Arbeitsabläufe durch Verkettung von PDF-Aktionen erstellen. Ideal für wiederkehrende Aufgaben." } }, + "landing": { + "addFiles": "Dateien hinzufügen", + "uploadFromComputer": "Vom Computer hochladen" + }, "viewPdf": { "tags": "anzeigen,lesen,kommentieren,text,bild", "title": "PDF anzeigen/bearbeiten", @@ -650,13 +633,20 @@ "merge": { "tags": "zusammenführen,seitenvorgänge,back end,serverseitig", "title": "Zusammenführen", - "header": "Mehrere PDFs zusammenführen (2+)", - "sortByName": "Nach Namen sortieren", - "sortByDate": "Nach Datum sortieren", - "removeCertSign": "Digitale Signatur in der zusammengeführten Datei entfernen?", + "removeDigitalSignature": "Digitale Signatur in der zusammengeführten Datei entfernen?", + "generateTableOfContents": "Inhaltsverzeichnis in der zusammengeführten Datei erstellen?", "submit": "Zusammenführen", "sortBy": { - "filename": "Dateiname" + "description": "Dateien werden in der Reihenfolge zusammengeführt, in der sie ausgewählt wurden. Ziehen Sie zum Neuordnen oder sortieren Sie unten.", + "label": "Sortieren nach", + "filename": "Dateiname", + "dateModified": "Änderungsdatum", + "ascending": "Aufsteigend", + "descending": "Absteigend", + "sort": "Sortieren" + }, + "error": { + "failed": "Ein Fehler ist beim Zusammenführen der PDFs aufgetreten." } }, "split": { @@ -676,25 +666,201 @@ "splitPages": "Geben Sie die Seiten an, an denen aufgeteilt werden soll:", "submit": "Aufteilen", "steps": { + "chooseMethod": "Methode wählen", "settings": "Einstellungen" }, + "settings": { + "selectMethodFirst": "Bitte wählen Sie zuerst eine Aufteilungsmethode" + }, + "error": { + "failed": "Ein Fehler ist beim Aufteilen der PDF aufgetreten." + }, + "method": { + "label": "Aufteilungsmethode wählen", + "placeholder": "Auswählen, wie die PDF aufgeteilt werden soll" + }, "methods": { + "prefix": { + "splitAt": "Aufteilen bei", + "splitBy": "Aufteilen nach" + }, + "byPages": { + "name": "Seitenzahlen", + "desc": "Spezifische Seiten extrahieren (1,3,5-10)", + "tooltip": "Seitenzahlen durch Kommas getrennt oder Bereiche mit Bindestrichen eingeben" + }, + "bySections": { + "name": "Abschnitte", + "desc": "Seiten in Rasterabschnitte unterteilen", + "tooltip": "Jede Seite in horizontale und vertikale Abschnitte aufteilen" + }, "bySize": { - "name": "Dateigröße" + "name": "Dateigröße", + "desc": "Maximale Dateigröße begrenzen", + "tooltip": "Maximale Dateigröße angeben (z.B. 10MB, 500KB)" + }, + "byPageCount": { + "name": "Seitenanzahl", + "desc": "Feste Seitenzahl pro Datei", + "tooltip": "Geben Sie die Anzahl der Seiten für jede geteilte Datei ein" + }, + "byDocCount": { + "name": "Dokumentenanzahl", + "desc": "Bestimmte Anzahl von Dateien erstellen", + "tooltip": "Geben Sie ein, wie viele Dateien Sie erstellen möchten" + }, + "byChapters": { + "name": "Kapitel", + "desc": "An Lesezeichen-Grenzen aufteilen", + "tooltip": "Verwendet PDF-Lesezeichen zur Bestimmung der Teilungspunkte" + }, + "byPageDivider": { + "name": "Seitenteiler", + "desc": "Automatisch mit Trennblättern aufteilen", + "tooltip": "Verwenden Sie QR-Code-Trennblätter zwischen Dokumenten beim Scannen" } }, "value": { "fileSize": { - "label": "Dateigröße" + "label": "Dateigröße", + "placeholder": "z.B. 10MB, 500KB" + }, + "pageCount": { + "label": "Seiten pro Datei", + "placeholder": "z.B. 5, 10" + }, + "docCount": { + "label": "Anzahl der Dateien", + "placeholder": "z.B. 3, 5" + } + }, + "tooltip": { + "header": { + "title": "Übersicht der Aufteilungsmethoden" + }, + "byPages": { + "title": "Nach Seitenzahlen aufteilen", + "text": "Teilen Sie Ihre PDF an bestimmten Seitenzahlen. Mit 'n' wird nach Seite n geteilt. Mit 'n-m' wird vor Seite n und nach Seite m geteilt.", + "bullet1": "Einzelne Teilungspunkte: 3,7 (teilt nach Seite 3 und 7)", + "bullet2": "Bereichs-Teilungspunkte: 3-8 (teilt vor Seite 3 und nach Seite 8)", + "bullet3": "Gemischt: 2,5-10,15 (teilt nach Seite 2, vor Seite 5, nach Seite 10 und nach Seite 15)" + }, + "bySections": { + "title": "Nach Rasterabschnitten aufteilen", + "text": "Teilen Sie jede Seite in ein Raster von Abschnitten auf. Nützlich zum Aufteilen von Dokumenten mit mehreren Spalten oder zum Extrahieren bestimmter Bereiche.", + "bullet1": "Horizontal: Anzahl der zu erstellenden Zeilen", + "bullet2": "Vertikal: Anzahl der zu erstellenden Spalten", + "bullet3": "Zusammenführen: Alle Abschnitte in eine PDF kombinieren" + }, + "bySize": { + "title": "Nach Dateigröße aufteilen", + "text": "Erstellen Sie mehrere PDFs, die eine bestimmte Dateigröße nicht überschreiten. Ideal für Dateigrößenbeschränkungen oder E-Mail-Anhänge.", + "bullet1": "Verwenden Sie MB für größere Dateien (z.B. 10MB)", + "bullet2": "Verwenden Sie KB für kleinere Dateien (z.B. 500KB)", + "bullet3": "Das System teilt an Seitengrenzen" + }, + "byCount": { + "title": "Nach Anzahl aufteilen", + "text": "Erstellen Sie mehrere PDFs mit einer bestimmten Anzahl von Seiten oder Dokumenten jeweils.", + "bullet1": "Seitenanzahl: Feste Anzahl von Seiten pro Datei", + "bullet2": "Dokumentenanzahl: Feste Anzahl von Ausgabedateien", + "bullet3": "Nützlich für Stapelverarbeitungs-Workflows" + }, + "byChapters": { + "title": "Nach Kapiteln aufteilen", + "text": "Verwenden Sie PDF-Lesezeichen zum automatischen Teilen an Kapitelgrenzen. Erfordert PDFs mit Lesezeichen-Struktur.", + "bullet1": "Lesezeichen-Ebene: Auf welcher Ebene geteilt wird (1=oberste Ebene)", + "bullet2": "Metadaten einschließen: Dokumenteigenschaften beibehalten", + "bullet3": "Duplikate zulassen: Wiederholte Lesezeichennamen behandeln" } } }, "rotate": { "tags": "serverseitig", "title": "PDF drehen", - "header": "PDF drehen", - "selectAngle": "Wählen Sie den Winkel (in Vielfachen von 90 Grad):", - "submit": "Herunterladen" + "submit": "Drehen", + "error": { + "failed": "Ein Fehler ist beim Drehen der PDF aufgetreten." + }, + "preview": { + "title": "Rotations-Vorschau" + }, + "rotateLeft": "Gegen den Uhrzeigersinn drehen", + "rotateRight": "Im Uhrzeigersinn drehen", + "tooltip": { + "header": { + "title": "Übersicht der Rotationseinstellungen" + }, + "description": { + "text": "Drehen Sie Ihre PDF-Seiten im oder gegen den Uhrzeigersinn in 90-Grad-Schritten. Alle Seiten in der PDF werden gedreht. Die Vorschau zeigt, wie Ihr Dokument nach der Drehung aussehen wird." + }, + "controls": { + "title": "Steuerelemente", + "text": "Verwenden Sie die Drehschaltflächen zur Anpassung der Ausrichtung. Die linke Schaltfläche dreht gegen den Uhrzeigersinn, die rechte Schaltfläche dreht im Uhrzeigersinn. Jeder Klick dreht um 90 Grad." + } + } + }, + "convert": { + "title": "Umwandeln", + "desc": "Dateien zwischen verschiedenen Formaten konvertieren", + "files": "Dateien", + "selectFilesPlaceholder": "Wählen Sie Dateien in der Hauptansicht aus, um zu beginnen", + "settings": "Einstellungen", + "conversionCompleted": "Konvertierung abgeschlossen", + "results": "Ergebnisse", + "defaultFilename": "konvertierte_datei", + "conversionResults": "Konvertierungsergebnisse", + "convertFrom": "Konvertieren von", + "convertTo": "Konvertieren zu", + "sourceFormatPlaceholder": "Quellformat", + "targetFormatPlaceholder": "Zielformat", + "selectSourceFormatFirst": "Wählen Sie zuerst ein Quellformat aus", + "outputOptions": "Ausgabeoptionen", + "pdfOptions": "PDF-Optionen", + "imageOptions": "Bildoptionen", + "colorType": "Farbtyp", + "color": "Farbe", + "greyscale": "Graustufen", + "blackwhite": "Schwarz-Weiß", + "output": "Ausgabe", + "single": "Einzeln", + "multiple": "Mehrfach", + "fitOption": "Anpassungsoption", + "maintainAspectRatio": "Seitenverhältnis beibehalten", + "fitDocumentToPage": "Dokument an Seite anpassen", + "fillPage": "Seite füllen", + "autoRotate": "Automatisch drehen", + "autoRotateDescription": "Bilder automatisch drehen, um besser auf die PDF-Seite zu passen", + "combineImages": "Bilder kombinieren", + "combineImagesDescription": "Alle Bilder in eine PDF kombinieren oder separate PDFs für jedes Bild erstellen", + "webOptions": "Web-zu-PDF-Optionen", + "zoomLevel": "Zoomstufe", + "emailOptions": "E-Mail-zu-PDF-Optionen", + "includeAttachments": "E-Mail-Anhänge einschließen", + "maxAttachmentSize": "Maximale Anhangsgröße (MB)", + "includeAllRecipients": "CC- und BCC-Empfänger in der Kopfzeile einschließen", + "downloadHtml": "HTML-Zwischendatei anstatt PDF herunterladen", + "pdfaOptions": "PDF/A-Optionen", + "outputFormat": "Ausgabeformat", + "pdfaNote": "PDF/A-1b ist kompatibler, PDF/A-2b unterstützt mehr Funktionen.", + "pdfaDigitalSignatureWarning": "Das PDF enthält eine digitale Signatur. Sie wird im nächsten Schritt entfernt.", + "fileFormat": "Dateiformat", + "wordDoc": "Word-Dokument", + "wordDocExt": "Word-Dokument (.docx)", + "odpExt": "OpenDocument Präsentation (.odp)", + "txtExt": "Einfacher Text (.txt)", + "selectedFiles": "Ausgewählte Dateien", + "noFileSelected": "Keine Datei ausgewählt. Verwenden Sie das Dateipanel, um Dateien hinzuzufügen.", + "convertFiles": "Dateien konvertieren", + "converting": "Konvertiere...", + "downloadConverted": "Konvertierte Datei herunterladen", + "errorNoFiles": "Bitte wählen Sie mindestens eine Datei zum Konvertieren aus.", + "errorNoFormat": "Bitte wählen Sie sowohl Quell- als auch Zielformat aus.", + "errorNotSupported": "Konvertierung von {{from}} zu {{to}} wird nicht unterstützt.", + "images": "Bilder", + "officeDocs": "Office-Dokumente (Word, Excel, PowerPoint)", + "imagesExt": "Bilder (JPG, PNG, usw.)", + "grayscale": "Graustufen" }, "imageToPdf": { "tags": "konvertierung,img,jpg,bild,foto" @@ -744,30 +910,189 @@ "upload": "Bild hinzufügen", "submit": "Bild hinzufügen" }, + "attachments": { + "tags": "einbetten, anhängen, datei, anhang, anhänge", + "title": "Anhänge hinzufügen", + "header": "Anhänge hinzufügen", + "add": "Anhang hinzufügen", + "remove": "Anhang entfernen", + "embed": "Anhang einbetten", + "submit": "Anhänge hinzufügen" + }, "watermark": { - "tags": "text,wiederholend,beschriftung,besitzen,urheberrecht,marke,img,jpg,bild,foto", "title": "Wasserzeichen hinzufügen", - "header": "Wasserzeichen hinzufügen", - "customColor": "Benutzerdefinierte Textfarbe", - "selectText": { - "1": "PDF auswählen, dem ein Wasserzeichen hinzugefügt werden soll:", - "2": "Wasserzeichen Text:", - "3": "Schriftgröße:", - "4": "Drehung (0-360):", - "5": "breiteSpacer (horizontaler Abstand zwischen den einzelnen Wasserzeichen):", - "6": "höheSpacer (vertikaler Abstand zwischen den einzelnen Wasserzeichen):", - "7": "Deckkraft (0% - 100 %):", - "8": "Wasserzeichen Typ:", - "9": "Wasserzeichen-Bild:", - "10": "PDF in PDF-Bild konvertieren" - }, + "desc": "Text- oder Bildwasserzeichen zu PDF-Dateien hinzufügen", + "completed": "Wasserzeichen hinzugefügt", "submit": "Wasserzeichen hinzufügen", - "type": { - "1": "Text", - "2": "Bild" + "filenamePrefix": "wasserzeichen", + "error": { + "failed": "Ein Fehler ist beim Hinzufügen des Wasserzeichens zur PDF aufgetreten." + }, + "watermarkType": { + "image": "Bild" }, "settings": { - "fontSize": "Schriftgröße" + "type": "Wasserzeichen-Typ", + "text": { + "label": "Wasserzeichen-Text", + "placeholder": "Wasserzeichen-Text eingeben" + }, + "image": { + "label": "Wasserzeichen-Bild", + "choose": "Bild auswählen", + "selected": "Ausgewählt: {{filename}}" + }, + "fontSize": "Schriftgröße", + "size": "Größe", + "alphabet": "Schriftart/Sprache", + "color": "Wasserzeichen-Farbe", + "rotation": "Rotation (Grad)", + "opacity": "Deckkraft (%)", + "spacing": { + "horizontal": "Horizontaler Abstand", + "vertical": "Vertikaler Abstand" + }, + "convertToImage": "PDF-Seiten in Bilder umwandeln" + }, + "alphabet": { + "roman": "Römisch/Lateinisch", + "arabic": "Arabisch", + "japanese": "Japanisch", + "korean": "Koreanisch", + "chinese": "Chinesisch", + "thai": "Thailändisch" + }, + "steps": { + "type": "Wasserzeichen-Typ", + "wording": "Formulierung", + "textStyle": "Stil", + "formatting": "Formatierung", + "file": "Wasserzeichen-Datei" + }, + "results": { + "title": "Wasserzeichen-Ergebnisse" + }, + "tooltip": { + "language": { + "title": "Sprachunterstützung", + "text": "Wählen Sie die entsprechende Spracheinstellung, um eine ordnungsgemäße Schriftdarstellung für Ihren Text zu gewährleisten." + }, + "appearance": { + "title": "Darstellungseinstellungen", + "text": "Steuern Sie das Aussehen Ihres Wasserzeichens und die Einblendung in das Dokument.", + "bullet1": "Rotation: -360° bis 360° für geneigte Wasserzeichen", + "bullet2": "Deckkraft: 0-100% für Transparenz-Steuerung", + "bullet3": "Niedrigere Deckkraft erzeugt dezente Wasserzeichen" + }, + "spacing": { + "title": "Abstandssteuerung", + "text": "Passen Sie den Abstand zwischen wiederholten Wasserzeichen auf der Seite an.", + "bullet1": "Breitenabstand: Horizontaler Abstand zwischen Wasserzeichen", + "bullet2": "Höhenabstand: Vertikaler Abstand zwischen Wasserzeichen", + "bullet3": "Höhere Werte erzeugen weiter verteilte Muster" + }, + "type": { + "header": { + "title": "Wasserzeichen-Typ auswählen" + }, + "description": { + "title": "Wasserzeichen auswählen", + "text": "Wählen Sie je nach Bedarf zwischen Text- oder Bildwasserzeichen." + }, + "text": { + "title": "Text-Wasserzeichen", + "text": "Perfekt für das Hinzufügen von Urheberrechtshinweisen, Firmennamen oder Vertraulichkeitsetiketten. Unterstützt mehrere Sprachen und benutzerdefinierte Farben.", + "bullet1": "Anpassbare Schriftarten und Sprachen", + "bullet2": "Einstellbare Farben und Transparenz", + "bullet3": "Ideal für rechtliche oder Branding-Texte" + }, + "image": { + "title": "Bild-Wasserzeichen", + "text": "Verwenden Sie Logos, Stempel oder beliebige Bilder als Wasserzeichen. Hervorragend für Branding und visuelle Identifikation.", + "bullet1": "Laden Sie beliebige Bildformate hoch", + "bullet2": "Erhält die Bildqualität", + "bullet3": "Perfekt für Logos und Stempel" + } + }, + "wording": { + "header": { + "title": "Textinhalt" + }, + "text": { + "title": "Wasserzeichen-Text", + "text": "Geben Sie den Text ein, der als Wasserzeichen im gesamten Dokument angezeigt werden soll.", + "bullet1": "Halten Sie es kurz für bessere Lesbarkeit", + "bullet2": "Häufige Beispiele: 'VERTRAULICH', 'ENTWURF', Firmenname", + "bullet3": "Emoji-Zeichen werden nicht unterstützt und herausgefiltert" + } + }, + "textStyle": { + "header": { + "title": "Textstil" + }, + "color": { + "title": "Farbauswahl", + "text": "Wählen Sie eine Farbe, die einen guten Kontrast zum Dokumentinhalt bietet.", + "bullet1": "Hellgrau (#d3d3d3) für dezente Wasserzeichen", + "bullet2": "Schwarz oder dunkle Farben für hohen Kontrast", + "bullet3": "Benutzerdefinierte Farben für Branding-Zwecke" + }, + "language": { + "title": "Sprachunterstützung", + "text": "Wählen Sie die entsprechende Spracheinstellung für eine ordnungsgemäße Schriftdarstellung." + } + }, + "file": { + "header": { + "title": "Bild hochladen" + }, + "upload": { + "title": "Bildauswahl", + "text": "Laden Sie eine Bilddatei hoch, um sie als Wasserzeichen zu verwenden.", + "bullet1": "Unterstützt gängige Formate: PNG, JPG, GIF, BMP", + "bullet2": "PNG mit Transparenz funktioniert am besten", + "bullet3": "Bilder mit höherer Auflösung behalten die Qualität besser bei" + }, + "recommendations": { + "title": "Bewährte Praktiken", + "text": "Tipps für optimale Bild-Wasserzeichen-Ergebnisse.", + "bullet1": "Verwenden Sie Logos oder Stempel mit transparentem Hintergrund", + "bullet2": "Einfache Designs funktionieren besser als komplexe Bilder", + "bullet3": "Berücksichtigen Sie die endgültige Dokumentgröße bei der Auflösungswahl" + } + }, + "formatting": { + "header": { + "title": "Formatierung & Layout" + }, + "size": { + "title": "Größensteuerung", + "text": "Passen Sie die Größe Ihres Wasserzeichens (Text oder Bild) an.", + "bullet1": "Größere Größen erzeugen auffälligere Wasserzeichen" + }, + "appearance": { + "title": "Darstellungseinstellungen", + "text": "Steuern Sie das Aussehen Ihres Wasserzeichens und die Einblendung in das Dokument.", + "bullet1": "Rotation: -360° bis 360° für geneigte Wasserzeichen", + "bullet2": "Deckkraft: 0-100% für Transparenz-Steuerung", + "bullet3": "Niedrigere Deckkraft erzeugt dezente Wasserzeichen" + }, + "spacing": { + "title": "Abstandssteuerung", + "text": "Passen Sie den Abstand zwischen wiederholten Wasserzeichen auf der Seite an.", + "bullet1": "Horizontaler Abstand: Abstand zwischen Wasserzeichen von links nach rechts", + "bullet2": "Vertikaler Abstand: Abstand zwischen Wasserzeichen von oben nach unten", + "bullet3": "Höhere Werte erzeugen weiter verteilte Muster" + }, + "security": { + "title": "Sicherheitsoption", + "text": "Konvertieren Sie die endgültige PDF in ein bildbasiertes Format für erhöhte Sicherheit.", + "bullet1": "Verhindert Textauswahl und Kopieren", + "bullet2": "Macht Wasserzeichen schwerer entfernbar", + "bullet3": "Führt zu größeren Dateigrößen", + "bullet4": "Am besten für sensible oder urheberrechtlich geschützte Inhalte" + } + } } }, "permissions": { @@ -778,7 +1103,7 @@ "selectText": { "1": "Das zu ändernde PDF auswählen", "2": "Zu setzende Berechtigungen", - "3": "Das zusammensetzen des PDFs verhindern", + "3": "Das Zusammenstellen des PDFs verhindern", "4": "Inhaltsextrahierung verhindern", "5": "Inhaltsextrahierung zur Barrierefreiheit verhindern", "6": "Ausfüllen des Formulars verhindern", @@ -792,50 +1117,148 @@ "removePages": { "tags": "seiten entfernen,seiten löschen", "title": "Entfernen", + "pageNumbers": { + "label": "Zu entfernende Seiten", + "placeholder": "z.B. 1,3,5-8,10", + "error": "Ungültiges Seitenzahlenformat. Verwenden Sie Zahlen, Bereiche (1-5) oder mathematische Ausdrücke (2n+1)" + }, + "filenamePrefix": "seiten_entfernt", + "files": { + "placeholder": "Wählen Sie eine PDF-Datei in der Hauptansicht aus, um zu beginnen" + }, + "settings": { + "title": "Einstellungen" + }, + "tooltip": { + "header": { + "title": "Einstellungen zum Entfernen von Seiten" + }, + "pageNumbers": { + "title": "Seitenauswahl", + "text": "Geben Sie an, welche Seiten aus Ihrer PDF entfernt werden sollen. Sie können einzelne Seiten, Bereiche oder mathematische Ausdrücke verwenden.", + "bullet1": "Einzelne Seiten: 1,3,5 (entfernt Seiten 1, 3 und 5)", + "bullet2": "Seitenbereiche: 1-5,10-15 (entfernt Seiten 1-5 und 10-15)", + "bullet3": "Mathematisch: 2n+1 (entfernt ungerade Seiten)", + "bullet4": "Offene Bereiche: 5- (entfernt von Seite 5 bis zum Ende)" + }, + "examples": { + "title": "Häufige Beispiele", + "text": "Hier sind einige häufige Seitenauswahlmuster:", + "bullet1": "Erste Seite entfernen: 1", + "bullet2": "Letzte 3 Seiten entfernen: -3", + "bullet3": "Jede zweite Seite entfernen: 2n", + "bullet4": "Bestimmte verstreute Seiten entfernen: 1,5,10,15" + }, + "safety": { + "title": "Sicherheitstipps", + "text": "Wichtige Überlegungen beim Entfernen von Seiten:", + "bullet1": "Zeigen Sie immer eine Vorschau Ihrer Auswahl vor der Verarbeitung an", + "bullet2": "Bewahren Sie eine Sicherungskopie Ihrer ursprünglichen Datei auf", + "bullet3": "Seitennummern beginnen bei 1, nicht bei 0", + "bullet4": "Ungültige Seitennummern werden ignoriert" + } + }, + "error": { + "failed": "Ein Fehler ist beim Entfernen der Seiten aufgetreten." + }, + "results": { + "title": "Ergebnisse der Seitenentfernung" + }, "submit": "Entfernen" }, - "addPassword": { - "tags": "sicher,sicherheit", - "title": "Passwort hinzufügen", - "header": "Passwort hinzufügen (Verschlüsseln)", - "selectText": { - "1": "Das zu verschlüsselnde PDF auswählen", - "2": "Passwort", - "3": "Länge des Schlüssels", - "4": "Größere Werte sind stärker, aber niedrigere Werte sind besser kompatibel.", - "5": "Zu setzende Berechtigungen", - "6": "Das zusammensetzen des PDFs verhindern", - "7": "Inhaltsextrahierung verhindern", - "8": "Inhaltsextrahierung zur Barrierefreiheit verhindern", - "9": "Ausfüllen des Formulars verhindern", - "10": "Modifizierung verhindern", - "11": "Ändern von Kommentaren verhindern", - "12": "Drucken verhindern", - "13": "Drucken verschiedener Formate verhindern", - "14": "Passwort des Besitzers", - "15": "Schränkt ein, was mit dem Dokument gemacht werden kann, sobald es geöffnet ist (wird nicht von allen Leseprogrammen unterstützt)", - "16": "Schränkt das Öffnen des Dokuments selbst ein" - }, - "submit": "Verschlüsseln", + "pageSelection": { "tooltip": { - "permissions": { - "title": "Berechtigungen ändern" + "header": { + "title": "Anleitung zur Seitenauswahl" + }, + "basic": { + "title": "Grundlegende Verwendung", + "text": "Wählen Sie bestimmte Seiten aus Ihrem PDF-Dokument mit einfacher Syntax.", + "bullet1": "Einzelne Seiten: 1,3,5", + "bullet2": "Seitenbereiche: 3-6 oder 10-15", + "bullet3": "Alle Seiten: all" + }, + "advanced": { + "title": "Erweiterte Funktionen" + }, + "tips": { + "title": "Tipps", + "text": "Behalten Sie diese Richtlinien im Hinterkopf:", + "bullet1": "Seitennummern beginnen bei 1 (nicht bei 0)", + "bullet2": "Leerzeichen werden automatisch entfernt", + "bullet3": "Ungültige Ausdrücke werden ignoriert" + }, + "syntax": { + "title": "Syntax-Grundlagen", + "text": "Verwenden Sie Zahlen, Bereiche, Schlüsselwörter und Progressionen (n beginnt bei 0). Klammern werden unterstützt.", + "bullets": { + "numbers": "Zahlen/Bereiche: 5, 10-20", + "keywords": "Schlüsselwörter: odd, even", + "progressions": "Progressionen: 3n, 4n+1" + } + }, + "operators": { + "title": "Operatoren", + "text": "AND hat höhere Priorität als Komma. NOT gilt innerhalb des Dokumentbereichs.", + "and": "AND: & oder \"and\" — erfordern beide Bedingungen (z.B. 1-50 & even)", + "comma": "Komma: , oder | — kombiniert Auswahlen (z.B. 1-10, 20)", + "not": "NOT: ! oder \"not\" — schließt Seiten aus (z.B. 3n & not 30)" + }, + "examples": { + "title": "Beispiele" } } }, - "removePassword": { - "tags": "sichern,entschlüsseln,sicherheit,passwort aufheben,passwort löschen", - "title": "Passwort entfernen", - "header": "Passwort entfernen (Entschlüsseln)", - "selectText": { - "1": "Das zu entschlüsselnde PDF auswählen", - "2": "Passwort" + "bulkSelection": { + "header": { + "title": "Anleitung zur Seitenauswahl" }, - "submit": "Entfernen", - "desc": "Den Passwortschutz eines PDFs entfernen", - "password": { - "stepTitle": "Passwort entfernen", - "label": "Aktuelles Passwort" + "syntax": { + "title": "Syntax-Grundlagen", + "text": "Verwenden Sie Zahlen, Bereiche, Schlüsselwörter und Progressionen (n beginnt bei 0). Klammern werden unterstützt.", + "bullets": { + "numbers": "Zahlen/Bereiche: 5, 10-20", + "keywords": "Schlüsselwörter: odd, even", + "progressions": "Progressionen: 3n, 4n+1" + } + }, + "operators": { + "title": "Operatoren", + "text": "AND hat höhere Priorität als Komma. NOT gilt innerhalb des Dokumentbereichs.", + "and": "AND: & oder \"and\" — erfordern beide Bedingungen (z.B. 1-50 & even)", + "comma": "Komma: , oder | — kombiniert Auswahlen (z.B. 1-10, 20)", + "not": "NOT: ! oder \"not\" — schließt Seiten aus (z.B. 3n & not 30)" + }, + "examples": { + "title": "Beispiele", + "first50": "Erste 50", + "last50": "Letzte 50", + "every3rd": "Jede 3.", + "oddWithinExcluding": "Ungerade innerhalb 1-20 ausgenommen 5-7", + "combineSets": "Mengen kombinieren" + }, + "firstNPages": { + "title": "Erste N Seiten", + "placeholder": "Anzahl der Seiten" + }, + "lastNPages": { + "title": "Letzte N Seiten", + "placeholder": "Anzahl der Seiten" + }, + "everyNthPage": { + "title": "Jede N-te Seite", + "placeholder": "Schrittgröße" + }, + "range": { + "title": "Bereich", + "fromPlaceholder": "Von", + "toPlaceholder": "Bis" + }, + "keywords": { + "title": "Schlüsselwörter" + }, + "advanced": { + "title": "Erweitert" } }, "compressPdfs": { @@ -845,28 +1268,140 @@ "tags": "entfernen,löschen,form,feld,schreibgeschützt", "title": "Entfernen Sie schreibgeschützte Formfelder", "header": "Schreibgeschützte PDF-Formfelder entfernen", - "submit": "Remove" + "submit": "Entfernen", + "description": "Dieses Tool entfernt Nur-Lese-Beschränkungen von PDF-Formularfeldern und macht sie bearbeitbar und ausfüllbar.", + "filenamePrefix": "entsperrte_formulare", + "files": { + "placeholder": "Wählen Sie eine PDF-Datei in der Hauptansicht aus, um zu beginnen" + }, + "error": { + "failed": "Ein Fehler ist beim Entsperren der PDF-Formulare aufgetreten." + }, + "results": { + "title": "Entsperrte Formulare - Ergebnisse" + } }, "changeMetadata": { "tags": "titel,autor,datum,erstellung,uhrzeit,herausgeber,produzent,statistiken", - "title": "Titel:", "header": "Metadaten ändern", - "selectText": { - "1": "Bitte bearbeiten Sie die Variablen, die Sie ändern möchten", - "2": "Alle Metadaten löschen", - "3": "Benutzerdefinierte Metadaten anzeigen:", - "4": "Andere Metadaten:", - "5": "Benutzerdefinierten Metadateneintrag hinzufügen" + "submit": "Ändern", + "filenamePrefix": "metadaten", + "settings": { + "title": "Metadaten-Einstellungen" }, - "author": "Autor:", - "creationDate": "Erstellungsdatum (JJJJ/MM/TT HH:mm:ss):", - "creator": "Ersteller:", - "keywords": "Schlüsselwörter:", - "modDate": "Änderungsdatum (JJJJ/MM/TT HH:mm:ss):", - "producer": "Produzent:", - "subject": "Betreff:", - "trapped": "Gefangen:", - "submit": "Ändern" + "standardFields": { + "title": "Standardfelder" + }, + "deleteAll": { + "label": "Vorhandene Metadaten entfernen", + "checkbox": "Alle Metadaten löschen" + }, + "title": { + "label": "Titel", + "placeholder": "Dokumententitel" + }, + "author": { + "label": "Autor", + "placeholder": "Dokumentautor" + }, + "subject": { + "label": "Betreff", + "placeholder": "Dokumentbetreff" + }, + "keywords": { + "label": "Schlüsselwörter", + "placeholder": "Dokumentschlüsselwörter" + }, + "creator": { + "label": "Ersteller", + "placeholder": "Dokumentersteller" + }, + "producer": { + "label": "Produzent", + "placeholder": "Dokumentproduzent" + }, + "dates": { + "title": "Datumsfelder" + }, + "creationDate": { + "label": "Erstellungsdatum", + "placeholder": "Erstellungsdatum" + }, + "modificationDate": { + "label": "Änderungsdatum", + "placeholder": "Änderungsdatum" + }, + "trapped": { + "label": "Trapped-Status", + "unknown": "Unbekannt" + }, + "advanced": { + "title": "Erweiterte Optionen" + }, + "customFields": { + "title": "Benutzerdefinierte Metadaten", + "description": "Benutzerdefinierte Metadatenfelder zum Dokument hinzufügen", + "add": "Feld hinzufügen", + "key": "Schlüssel", + "keyPlaceholder": "Benutzerdefinierter Schlüssel", + "value": "Wert", + "valuePlaceholder": "Benutzerdefinierter Wert", + "remove": "Entfernen" + }, + "results": { + "title": "Aktualisierte PDFs" + }, + "error": { + "failed": "Ein Fehler ist beim Ändern der PDF-Metadaten aufgetreten." + }, + "tooltip": { + "header": { + "title": "PDF-Metadaten Übersicht" + }, + "standardFields": { + "title": "Standardfelder", + "text": "Häufige PDF-Metadatenfelder, die das Dokument beschreiben.", + "bullet1": "Titel: Dokumentname oder Überschrift", + "bullet2": "Autor: Person, die das Dokument erstellt hat", + "bullet3": "Betreff: Kurze Beschreibung des Inhalts", + "bullet4": "Schlüsselwörter: Suchbegriffe für das Dokument", + "bullet5": "Ersteller/Produzent: Software zur Erstellung der PDF" + }, + "dates": { + "title": "Datumsfelder", + "text": "Wann das Dokument erstellt und geändert wurde.", + "bullet1": "Erstellungsdatum: Wann das ursprüngliche Dokument erstellt wurde", + "bullet2": "Änderungsdatum: Wann zuletzt geändert" + }, + "options": { + "title": "Zusätzliche Optionen", + "text": "Benutzerdefinierte Felder und Datenschutzkontrollen.", + "bullet1": "Benutzerdefinierte Metadaten: Fügen Sie Ihre eigenen Schlüssel-Wert-Paare hinzu", + "bullet2": "Trapped-Status: Einstellung für hochwertigen Druck", + "bullet3": "Alle löschen: Alle Metadaten für Datenschutz entfernen" + }, + "deleteAll": { + "title": "Vorhandene Metadaten entfernen", + "text": "Vollständige Löschung der Metadaten zum Schutz der Privatsphäre." + }, + "customFields": { + "title": "Benutzerdefinierte Metadaten", + "text": "Fügen Sie Ihre eigenen benutzerdefinierten Schlüssel-Wert-Metadatenpaare hinzu.", + "bullet1": "Fügen Sie beliebige benutzerdefinierte Felder hinzu, die für Ihr Dokument relevant sind", + "bullet2": "Beispiele: Abteilung, Projekt, Version, Status", + "bullet3": "Sowohl Schlüssel als auch Wert sind für jeden Eintrag erforderlich" + }, + "advanced": { + "title": "Erweiterte Optionen", + "trapped": { + "title": "Trapped-Status", + "description": "Zeigt an, ob das Dokument für hochwertigen Druck vorbereitet ist.", + "bullet1": "True: Dokument wurde für den Druck getrapped", + "bullet2": "False: Dokument wurde nicht getrapped", + "bullet3": "Unknown: Trapped-Status ist nicht angegeben" + } + } + } }, "fileToPDF": { "tags": "transformation,format,dokument,bild,folie,text,konvertierung,büro,dokumente,word,excel,powerpoint", @@ -880,6 +1415,7 @@ "ocr": { "tags": "erkennung,text,bild,scannen,lesen,identifizieren,erkennung,bearbeitbar", "title": "OCR / Scan-Bereinigung", + "desc": "Cleanup scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu", "header": "Scans bereinigen / OCR (Optical Character Recognition)", "selectText": { "1": "Sprachen auswählen, die im PDF erkannt werden sollen (die aufgelisteten sind die aktuell erkannten):", @@ -898,22 +1434,85 @@ "help": "Bitte lesen Sie diese Dokumentation, um zu erfahren, wie Sie dies für andere Sprachen verwenden und/oder nicht in Docker verwenden können", "credit": "Dieser Dienst verwendet qpdf und Tesseract für OCR.", "submit": "PDF mit OCR verarbeiten", - "desc": "Cleanup scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu", + "operation": { + "submit": "OCR verarbeiten und überprüfen" + }, + "results": { + "title": "OCR-Ergebnisse" + }, + "languagePicker": { + "additionalLanguages": "Suchen Sie nach zusätzlichen Sprachen?", + "viewSetupGuide": "Setup-Anleitung ansehen →" + }, "settings": { "title": "Einstellungen", "ocrMode": { - "label": "OCR-Modus" + "label": "OCR-Modus", + "auto": "Auto (Textebenen überspringen)", + "force": "Erzwingen (alle neu per OCR erfassen, Text ersetzen)", + "strict": "Strikt (abbrechen, wenn Text gefunden wird)" }, "languages": { - "label": "Sprachen" + "label": "Sprachen", + "placeholder": "Sprachen auswählen" + }, + "compatibilityMode": { + "label": "Kompatibilitätsmodus" + }, + "advancedOptions": { + "label": "Verarbeitungsoptionen", + "sidecar": "Textdatei erstellen", + "deskew": "Seiten entzerren", + "clean": "Eingabedatei bereinigen", + "cleanFinal": "Endgültige Ausgabe bereinigen" } }, "tooltip": { + "header": { + "title": "Übersicht der OCR-Einstellungen" + }, "mode": { - "title": "OCR-Modus" + "title": "OCR-Modus", + "text": "Optical Character Recognition (OCR) hilft Ihnen dabei, gescannte oder als Screenshot erfasste Seiten in Text umzuwandeln, den Sie durchsuchen, kopieren oder markieren können.", + "bullet1": "Auto überspringt Seiten, die bereits Textebenen enthalten.", + "bullet2": "Erzwingen führt OCR auf jeder Seite erneut durch und ersetzt den gesamten Text.", + "bullet3": "Strikt hält an, wenn auswählbarer Text gefunden wird." }, "languages": { - "title": "Sprachen" + "title": "Sprachen", + "text": "Verbessern Sie die OCR-Genauigkeit, indem Sie die erwarteten Sprachen angeben. Wählen Sie eine oder mehrere Sprachen zur Unterstützung der Erkennung." + }, + "output": { + "title": "Ausgabe", + "text": "Entscheiden Sie, wie die Textausgabe formatiert werden soll:", + "bullet1": "Durchsuchbare PDF bettet Text hinter dem ursprünglichen Bild ein.", + "bullet2": "HOCR XML gibt eine strukturierte maschinenlesbare Datei zurück.", + "bullet3": "Reiner Text-Sidecar erstellt eine separate .txt-Datei mit unverarbeitetem Inhalt." + }, + "advanced": { + "header": { + "title": "Erweiterte OCR-Verarbeitung" + }, + "compatibility": { + "title": "Kompatibilitätsmodus", + "text": "Verwendet OCR 'Sandwich PDF'-Modus: führt zu größeren Dateien, ist aber zuverlässiger bei bestimmten Sprachen und älterer PDF-Software. Standardmäßig verwenden wir hOCR für kleinere, moderne PDFs." + }, + "sidecar": { + "title": "Textdatei erstellen", + "text": "Generiert eine separate .txt-Datei neben der PDF, die den gesamten extrahierten Textinhalt für einfachen Zugriff und Verarbeitung enthält." + }, + "deskew": { + "title": "Seiten begradigen", + "text": "Korrigiert automatisch schiefe oder geneigte Seiten, um die OCR-Genauigkeit zu verbessern. Nützlich für gescannte Dokumente, die nicht perfekt ausgerichtet waren." + }, + "clean": { + "title": "Eingabedatei bereinigen", + "text": "Verarbeitet die Eingabe vor, indem Rauschen entfernt, Kontrast verbessert und das Bild für bessere OCR-Erkennung vor der Verarbeitung optimiert wird." + }, + "cleanFinal": { + "title": "Endgültige Ausgabe bereinigen", + "text": "Nachbearbeitung der finalen PDF durch Entfernung von OCR-Artefakten und Optimierung der Textebene für bessere Lesbarkeit und kleinere Dateigröße." + } } } }, @@ -956,7 +1555,7 @@ "submit": "Konvertieren" }, "PDFToText": { - "tags": "richformat,richtextformat,rich text format", + "tags": "reichformat,richtextformat,rich text format", "title": "PDF in Text/RTF", "header": "PDF in Text/RTF", "selectText": { @@ -1022,30 +1621,109 @@ "flatten": { "tags": "statisch,deaktivieren,nicht interaktiv,optimieren", "title": "Abflachen", - "header": "PDFs reduzieren", + "header": "PDFs abflachen", "flattenOnlyForms": "Nur Formulare abflachen", "submit": "Abflachen", + "filenamePrefix": "abgeflacht", + "files": { + "placeholder": "Wählen Sie eine PDF-Datei in der Hauptansicht aus, um zu beginnen" + }, "steps": { "settings": "Einstellungen" }, "options": { - "flattenOnlyForms": "Nur Formulare abflachen" + "stepTitle": "Abflachungs-Optionen", + "title": "Abflachungs-Optionen", + "flattenOnlyForms.desc": "Nur Formularfelder vereinfachen, andere interaktive Elemente unverändert lassen", + "note": "Das Abflachen entfernt interaktive Elemente aus der PDF und macht sie nicht mehr bearbeitbar." + }, + "results": { + "title": "Reduzierungs-Ergebnisse" + }, + "error": { + "failed": "Ein Fehler ist beim Abflachen der PDF aufgetreten." + }, + "tooltip": { + "header": { + "title": "Über das Abflachen von PDFs" + }, + "description": { + "title": "Was bewirkt das Abflachen?", + "text": "Das Abflachen macht Ihre PDF nicht bearbeitbar, indem ausfüllbare Formulare und Buttons in normalen Text und Bilder umgewandelt werden. Die PDF sieht genau gleich aus, aber niemand kann die Formulare mehr ändern oder ausfüllen. Perfekt zum Teilen ausgefüllter Formulare, Erstellen finaler Dokumente für Aufzeichnungen oder um sicherzustellen, dass die PDF überall gleich aussieht.", + "bullet1": "Textfelder werden zu normalem Text (nicht bearbeitbar)", + "bullet2": "Checkboxen und Buttons werden zu Bildern", + "bullet3": "Ideal für Endversionen, die nicht geändert werden sollen", + "bullet4": "Gewährleistet einheitliches Aussehen auf allen Geräten" + }, + "formsOnly": { + "title": "Was bedeutet 'Nur Formulare reduzieren'?", + "text": "Diese Option entfernt nur die Möglichkeit, Formulare auszufüllen, lässt aber andere Funktionen wie das Anklicken von Links, das Anzeigen von Lesezeichen und das Lesen von Kommentaren weiterhin funktionieren.", + "bullet1": "Formulare werden nicht mehr bearbeitbar", + "bullet2": "Links funktionieren beim Anklicken weiterhin", + "bullet3": "Kommentare und Notizen bleiben sichtbar", + "bullet4": "Lesezeichen helfen weiterhin bei der Navigation" + } } }, "repair": { "tags": "reparieren,wiederherstellen,korrigieren,wiederherstellen", "title": "Reparieren", "header": "PDFs reparieren", - "submit": "Reparieren" + "submit": "Reparieren", + "description": "Dieses Tool versucht, beschädigte oder korrupte PDF-Dateien zu reparieren. Keine zusätzlichen Einstellungen erforderlich.", + "filenamePrefix": "repariert", + "files": { + "placeholder": "Wählen Sie eine PDF-Datei in der Hauptansicht aus, um zu beginnen" + }, + "error": { + "failed": "Ein Fehler ist beim Reparieren der PDF aufgetreten." + }, + "results": { + "title": "Reparatur-Ergebnisse" + } }, "removeBlanks": { "tags": "aufräumen,rationalisieren,nicht inhaltsreich,organisieren", "title": "Leere Seiten entfernen", "header": "Leere Seiten entfernen", - "threshold": "Schwellenwert:", - "thresholdDesc": "Schwellenwert zur Bestimmung, wie weiß ein weißer Pixel sein muss", - "whitePercent": "Weißprozentsatz (%):", - "whitePercentDesc": "Prozentsatz der Seite, die weiß sein muss, um entfernt zu werden", + "settings": { + "title": "Einstellungen" + }, + "threshold": { + "label": "Pixel-Weißheitsschwellwert" + }, + "whitePercent": { + "label": "Weiß-Prozentsatz-Schwellwert" + }, + "includeBlankPages": { + "label": "Erkannte leere Seiten einschließen" + }, + "tooltip": { + "header": { + "title": "Leere Seiten entfernen - Einstellungen" + }, + "threshold": { + "title": "Pixel-Weißheits-Schwellenwert", + "text": "Steuert, wie weiß ein Pixel sein muss, um als 'weiß' betrachtet zu werden. Dies hilft zu bestimmen, was als leerer Bereich auf der Seite zählt.", + "bullet1": "0 = Reines Schwarz (am restriktivsten)", + "bullet2": "128 = Mittelgrau", + "bullet3": "255 = Reines Weiß (am wenigsten restriktiv)" + }, + "whitePercent": { + "title": "Weiß-Prozentsatz-Schwellenwert", + "text": "Legt den Mindestprozentsatz weißer Pixel fest, der erforderlich ist, damit eine Seite als leer betrachtet und entfernt wird.", + "bullet1": "Niedrigere Werte (z.B. 80%) = Mehr Seiten entfernt", + "bullet2": "Höhere Werte (z.B. 95%) = Nur sehr leere Seiten entfernt", + "bullet3": "Verwenden Sie höhere Werte für Dokumente mit hellem Hintergrund" + }, + "includeBlankPages": { + "title": "Erkannte leere Seiten einschließen", + "text": "Wenn aktiviert, erstellt eine separate PDF mit allen leeren Seiten, die erkannt und aus dem ursprünglichen Dokument entfernt wurden.", + "bullet1": "Nützlich zur Überprüfung dessen, was entfernt wurde", + "bullet2": "Hilft bei der Überprüfung der Erkennungsgenauigkeit", + "bullet3": "Kann deaktiviert werden, um die Ausgabedateigröße zu reduzieren" + } + }, "submit": "Leere Seiten entfernen" }, "removeAnnotations": { @@ -1077,35 +1755,147 @@ }, "no": { "text": { - "message": "Ein oder beide ausgewählten PDFs enthalten keine Textinhalt. Wählen Sie bitte PDFs mit Text für die Vergleichsanalyse." + "message": "Ein oder beide ausgewählten PDFs enthalten keinen Textinhalt. Wählen Sie bitte PDFs mit Text für die Vergleichsanalyse." } } }, "certSign": { "tags": "authentifizieren,pem,p12,offiziell,verschlüsseln", "title": "Zertifikatsignierung", - "header": "Signieren Sie ein PDF mit Ihrem Zertifikat (in Arbeit)", - "selectPDF": "Wählen Sie eine PDF-Datei zum Signieren aus:", - "jksNote": "Hinweis: Wenn Ihr Zertifikatstyp unten nicht aufgeführt ist, konvertieren Sie ihn bitte mit dem Befehlszeilentool keytool in eine Java Keystore-Datei (.jks). Wählen Sie dann unten die Option „.jks-Datei“ aus.", - "selectKey": "Wählen Sie Ihre private Schlüsseldatei aus (PKCS#8-Format, könnte .pem oder .der sein):", - "selectCert": "Wählen Sie Ihre Zertifikatsdatei aus (X.509-Format, könnte .pem oder .der sein):", - "selectP12": "Wählen Sie Ihre PKCS#12-Keystore-Datei (.p12 oder .pfx) aus (optional, falls angegeben, sollte sie Ihren privaten Schlüssel und Ihr Zertifikat enthalten):", - "selectJKS": "Wählen Sie Ihre Java Keystore-Datei (.jks oder .keystore):", - "certType": "Zertifikattyp", - "password": "Geben Sie Ihr Keystore- oder Private-Key-Passwort ein (falls vorhanden):", - "showSig": "Signatur anzeigen", - "reason": "Grund", - "location": "Standort", - "name": "Name", - "showLogo": "Logo anzeigen", - "submit": "PDF signieren" + "filenamePrefix": "signiert", + "signMode": { + "stepTitle": "Signatur-Modus", + "tooltip": { + "header": { + "title": "Über PDF-Signaturen" + }, + "overview": { + "title": "Wie Signaturen funktionieren", + "text": "Beide Modi versiegeln das Dokument (alle Änderungen werden als Manipulation markiert) und zeichnen wer/wann/wie zur Prüfung auf. Das Vertrauen des Betrachters hängt von der Zertifikatskette ab." + }, + "manual": { + "title": "Manuell - Ihr eigenes Zertifikat verwenden", + "text": "Verwenden Sie Ihre eigenen Zertifikatsdateien für markengerechte Identität. Kann Vertrauenswürdig anzeigen, wenn Ihre CA/Kette erkannt wird.", + "use": "Verwendung für: kundenorientiert, rechtlich, Compliance." + }, + "auto": { + "title": "Automatisch - Sofort-Systemsiegel ohne Einrichtung", + "text": "Signiert mit einem server selbst-signierten Zertifikat. Gleiches manipulationssicheres Siegel und Prüfprotokoll; zeigt normalerweise Unverifiziert in Viewern an.", + "use": "Verwenden Sie dies, wenn: Sie Geschwindigkeit und eine konsistente interne Identität über Bewertungen und Aufzeichnungen hinweg benötigen." + }, + "rule": { + "title": "Faustregel", + "text": "Benötigen Sie den Vertrauenswürdig-Status beim Empfänger? Manuell. Benötigen Sie ein schnelles, manipulationssicheres Siegel und Prüfprotokoll ohne Setup? Auto." + } + } + }, + "certTypeStep": { + "stepTitle": "Zertifikat-Format" + }, + "certFiles": { + "stepTitle": "Zertifikat-Dateien" + }, + "appearance": { + "stepTitle": "Signatur-Erscheinungsbild", + "tooltip": { + "header": { + "title": "Über das Signatur-Erscheinungsbild" + }, + "invisible": { + "title": "Unsichtbare Signaturen", + "text": "Die Signatur wird aus Sicherheitsgründen zur PDF hinzugefügt, ist aber beim Betrachten des Dokuments nicht sichtbar. Perfekt für rechtliche Anforderungen, ohne das Erscheinungsbild des Dokuments zu ändern.", + "bullet1": "Bietet Sicherheit ohne visuelle Änderungen", + "bullet2": "Erfüllt rechtliche Anforderungen für digitale Signierung", + "bullet3": "Beeinflusst nicht das Dokumentenlayout oder -design" + }, + "visible": { + "title": "Sichtbare Signaturen", + "text": "Zeigt einen Signaturblock auf der PDF mit Ihrem Namen, Datum und optionalen Details. Nützlich, wenn die Leser deutlich sehen sollen, dass das Dokument signiert ist.", + "bullet1": "Zeigt Signierername und Datum auf dem Dokument", + "bullet2": "Kann Grund und Ort der Signierung enthalten", + "bullet3": "Wählen Sie, auf welcher Seite die Signatur platziert werden soll", + "bullet4": "Optionales Logo kann eingefügt werden" + } + } + }, + "sign": { + "submit": "PDF signieren", + "results": "Signierte PDF" + }, + "error": { + "failed": "Ein Fehler ist bei der Verarbeitung der Signaturen aufgetreten." + }, + "tooltip": { + "header": { + "title": "Über die Signatur-Verwaltung" + }, + "overview": { + "title": "Was kann dieses Tool?", + "text": "Dieses Tool ermöglicht es Ihnen zu prüfen, ob Ihre PDFs digital signiert sind, und neue digitale Signaturen hinzuzufügen. Digitale Signaturen beweisen, wer ein Dokument erstellt oder genehmigt hat und zeigen, ob es seit der Signierung geändert wurde.", + "bullet1": "Vorhandene Signaturen und deren Gültigkeit prüfen", + "bullet2": "Detaillierte Informationen über Signierer und Zertifikate anzeigen", + "bullet3": "Neue digitale Signaturen hinzufügen, um Ihre Dokumente zu sichern", + "bullet4": "Mehrere Dateien unterstützt mit einfacher Navigation" + }, + "validation": { + "title": "Signaturen überprüfen", + "text": "Wenn Sie Signaturen prüfen, zeigt Ihnen das Tool, ob sie gültig sind, wer das Dokument signiert hat, wann es signiert wurde und ob das Dokument seit der Signierung geändert wurde.", + "bullet1": "Zeigt, ob Signaturen gültig oder ungültig sind", + "bullet2": "Zeigt Signiererinformationen und Signierdatum an", + "bullet3": "Prüft, ob das Dokument nach der Signierung geändert wurde", + "bullet4": "Kann benutzerdefinierte Zertifikate für die Verifizierung verwenden" + }, + "signing": { + "title": "Signaturen hinzufügen", + "text": "Um eine PDF zu signieren, benötigen Sie ein digitales Zertifikat (wie PEM, PKCS12 oder JKS). Sie können wählen, ob Sie die Signatur auf dem Dokument sichtbar machen oder nur für die Sicherheit unsichtbar belassen.", + "bullet1": "Unterstützt PEM, PKCS12, JKS und Server-Zertifikatformate", + "bullet2": "Option, Signatur auf der PDF zu zeigen oder zu verstecken", + "bullet3": "Grund, Ort und Signierername hinzufügen", + "bullet4": "Wählen Sie, auf welcher Seite sichtbare Signaturen platziert werden sollen", + "bullet5": "Server-Zertifikat für einfache 'Mit Stirling-PDF signieren'-Option verwenden" + } + }, + "certType": { + "tooltip": { + "header": { + "title": "Über Zertifikat-Typen" + }, + "what": { + "title": "Was ist ein Zertifikat?", + "text": "Es ist eine sichere ID für Ihre Signatur, die beweist, dass Sie signiert haben. Es sei denn, Sie müssen per Zertifikat signieren, empfehlen wir eine andere sichere Methode wie Tippen, Zeichnen oder Hochladen." + }, + "which": { + "title": "Welche Option sollte ich verwenden?", + "text": "Wählen Sie das Format, das zu Ihrer Zertifikatdatei passt:", + "bullet1": "PKCS#12 (.p12 / .pfx) – eine kombinierte Datei (am häufigsten)", + "bullet2": "PFX (.pfx) – Microsofts Version von PKCS12", + "bullet3": "PEM – separate private-key und certificate .pem Dateien", + "bullet4": "JKS – Java .jks keystore für dev / CI-CD Workflows" + }, + "convert": { + "title": "Schlüssel nicht aufgelistet?", + "text": "Konvertieren Sie Ihre Datei mit keytool zu einem Java keystore (.jks), dann wählen Sie JKS." + } + } + } }, "removeCertSign": { "tags": "authentifizieren,PEM,P12,offiziell,entschlüsseln", "title": "Zertifikatsignatur entfernen", "header": "Digitales Zertifikat aus dem PDF entfernen", "selectPDF": "PDF-Datei auswählen:", - "submit": "Signatur entfernen" + "submit": "Signatur entfernen", + "description": "Dieses Tool entfernt digitale Zertifikatssignaturen aus Ihrem PDF-Dokument.", + "filenamePrefix": "unsigniert", + "files": { + "placeholder": "Wählen Sie eine PDF-Datei in der Hauptansicht aus, um zu beginnen" + }, + "error": { + "failed": "Ein Fehler ist beim Entfernen der Zertifikatssignaturen aufgetreten." + }, + "results": { + "title": "Zertifikat-Entfernungs-Ergebnisse" + } }, "pageLayout": { "tags": "zusammenführen,zusammensetzen,einzelansicht,organisieren", @@ -1115,8 +1905,100 @@ "addBorder": "Ränder hinzufügen", "submit": "Abschicken" }, + "bookletImposition": { + "tags": "broschüre,imposition,drucken,bindung,falten,signatur", + "title": "Broschüren-Ausschießen", + "header": "Broschüren-Anordnung", + "submit": "Broschüre erstellen", + "spineLocation": { + "label": "Rücken-Position", + "left": "Links (Standard)", + "right": "Rechts (RTL)" + }, + "doubleSided": { + "label": "Doppelseitiger Druck", + "tooltip": "Erstellt sowohl Vorder- als auch Rückseiten für ordnungsgemäßen Broschürendruck" + }, + "manualDuplex": { + "title": "Manueller Duplex-Modus", + "instructions": "Für Drucker ohne automatischen Duplex. Sie müssen dies zweimal ausführen:" + }, + "duplexPass": { + "label": "Druckdurchgang", + "first": "1. Durchgang", + "second": "2. Durchgang", + "firstInstructions": "Druckt Vorderseiten → stapelt mit der bedruckten Seite nach unten → erneut ausführen mit 2. Durchgang", + "secondInstructions": "Bedruckten Stapel mit der Seite nach unten einlegen → druckt Rückseiten" + }, + "rtlBinding": { + "label": "Rechts-nach-links-Bindung", + "tooltip": "Für Arabisch, Hebräisch oder andere Rechts-nach-links-Sprachen" + }, + "addBorder": { + "label": "Ränder um Seiten hinzufügen", + "tooltip": "Fügt Ränder um jeden Seitenbereich hinzu, um beim Schneiden und Ausrichten zu helfen" + }, + "addGutter": { + "label": "Bundsteg-Rand hinzufügen", + "tooltip": "Fügt inneren Randbereich für die Bindung hinzu" + }, + "gutterSize": { + "label": "Bundsteg-Größe (Punkte)" + }, + "flipOnShortEdge": { + "label": "An der kurzen Kante wenden (nur automatischer Duplex)", + "tooltip": "Aktivieren für Kurzkanten-Duplexdruck (nur automatischer Duplex - wird im manuellen Modus ignoriert)", + "manualNote": "Nicht erforderlich im manuellen Modus - Sie wenden den Stapel selbst" + }, + "advanced": { + "toggle": "Erweiterte Optionen" + }, + "paperSizeNote": "Die Papiergröße wird automatisch von Ihrer ersten Seite abgeleitet.", + "tooltip": { + "header": { + "title": "Broschüren-Erstellungsanleitung" + }, + "description": { + "title": "Was ist Broschüren-Ausschießen?", + "text": "Erstellt professionelle Broschüren durch Anordnung der Seiten in der korrekten Druckreihenfolge. Ihre PDF-Seiten werden 2-seitig auf Querformat-Blätter platziert, damit sie beim Falten und Binden in richtiger Reihenfolge wie ein echtes Buch lesbar sind." + }, + "example": { + "title": "Beispiel: 8-seitige Broschüre", + "text": "Ihr 8-seitiges Dokument wird zu 2 Blättern:", + "bullet1": "Blatt 1 Vorderseite: Seiten 8, 1 | Rückseite: Seiten 2, 7", + "bullet2": "Blatt 2 Vorderseite: Seiten 6, 3 | Rückseite: Seiten 4, 5", + "bullet3": "Wenn gefaltet & gestapelt: Liest sich 1→2→3→4→5→6→7→8" + }, + "printing": { + "title": "Drucken & Zusammenfügen", + "text": "Befolgen Sie diese Schritte für perfekte Broschüren:", + "bullet1": "Doppelseitig drucken mit 'An langer Kante wenden'", + "bullet2": "Blätter in Reihenfolge stapeln, in der Mitte falten", + "bullet3": "Entlang des gefalteten Rückens heften oder binden", + "bullet4": "Für Kurzkanten-Drucker: 'An kurzer Kante wenden'-Option aktivieren" + }, + "manualDuplex": { + "title": "Manueller Duplex (Einseitige Drucker)", + "text": "Für Drucker ohne automatischen Duplex:", + "bullet1": "'Doppelseitiger Druck' AUSSCHALTEN", + "bullet2": "'1. Durchgang' auswählen → Drucken → Mit bedruckter Seite nach unten stapeln", + "bullet3": "'2. Durchgang' auswählen → Stapel einlegen → Rückseiten drucken", + "bullet4": "Normal falten und zusammenfügen" + }, + "advanced": { + "title": "Erweiterte Optionen", + "text": "Ihre Broschüre feinabstimmen:", + "bullet1": "Rechts-nach-Links-Bindung: Für Arabisch, Hebräisch oder RTL-Sprachen", + "bullet2": "Ränder: Zeigt Schnittlinien zum Zuschneiden an", + "bullet3": "Bundsteg: Fügt Platz für Bindung/Heftung hinzu", + "bullet4": "Kurzkantenumschlag: Nur für automatische Duplex-Drucker" + } + }, + "error": { + "failed": "Ein Fehler ist bei der Erstellung der Broschüren-Anordnung aufgetreten." + } + }, "scalePages": { - "tags": "größe ändern,ändern,dimensionieren,anpassen", "title": "Seitengröße anpassen", "header": "Seitengröße anpassen", "pageSize": "Format der Seiten des Dokuments", @@ -1124,6 +2006,42 @@ "scaleFactor": "Zoomstufe (Ausschnitt) einer Seite", "submit": "Abschicken" }, + "adjustPageScale": { + "tags": "größe-ändern,modifizieren,dimension,anpassen", + "title": "Seitengröße anpassen", + "header": "Seitenskalierung anpassen", + "scaleFactor": { + "label": "Skalierungsfaktor" + }, + "pageSize": { + "label": "Ziel-Seitengröße", + "keep": "Ursprüngliche Größe beibehalten" + }, + "submit": "Seitenskalierung anpassen", + "error": { + "failed": "Ein Fehler ist beim Anpassen der Seitenskalierung aufgetreten." + }, + "tooltip": { + "header": { + "title": "Seitengröße-Einstellungen - Übersicht" + }, + "description": { + "title": "Beschreibung", + "text": "Größe des PDF-Inhalts anpassen und Seitenabmessungen ändern." + }, + "scaleFactor": { + "title": "Skalierungsfaktor", + "text": "Steuert, wie groß oder klein der Inhalt auf der Seite angezeigt wird. Der Inhalt wird skaliert und zentriert - wenn der skalierte Inhalt größer als die Seitengröße ist, kann er abgeschnitten werden.", + "bullet1": "1.0 = Originalgröße", + "bullet2": "0.5 = Halbe Größe (50% kleiner)", + "bullet3": "2.0 = Doppelte Größe (200% größer, kann abgeschnitten werden)" + }, + "pageSize": { + "title": "Ziel-Seitengröße", + "text": "Legt die Abmessungen der Ausgabe-PDF-Seiten fest. 'Ursprüngliche Größe beibehalten' behält die aktuellen Abmessungen bei, während andere Optionen auf Standard-Papierformate skalieren." + } + } + }, "add-page-numbers": { "tags": "paginieren,beschriften,organisieren,indizieren" }, @@ -1131,7 +2049,29 @@ "tags": "automatisch erkennen,header basiert,organisieren,neu kennzeichnen", "title": "PDF automatisch umbenennen", "header": "PDF automatisch umbenennen", - "submit": "Automatisch umbenennen" + "description": "Findet automatisch den Titel aus Ihrem PDF-Inhalt und verwendet ihn als Dateinamen.", + "submit": "Automatisch umbenennen", + "files": { + "placeholder": "Wählen Sie eine PDF-Datei in der Hauptansicht aus, um zu beginnen" + }, + "error": { + "failed": "Ein Fehler ist beim automatischen Umbenennen der PDF aufgetreten." + }, + "results": { + "title": "Auto-Umbenennung-Ergebnisse" + }, + "tooltip": { + "header": { + "title": "Wie Auto-Umbenennung funktioniert" + }, + "howItWorks": { + "title": "Intelligente Umbenennung", + "text": "Findet automatisch den Titel aus Ihrem PDF-Inhalt und verwendet ihn als Dateiname.", + "bullet1": "Sucht nach Text, der als Titel oder Überschrift erscheint", + "bullet2": "Erstellt einen sauberen, gültigen Dateinamen aus dem erkannten Titel", + "bullet3": "Behält den ursprünglichen Namen bei, wenn kein geeigneter Titel gefunden wird" + } + } }, "adjust-contrast": { "tags": "farbkorrektur,abstimmung,änderung,verbesserung" @@ -1140,7 +2080,36 @@ "tags": "trimmen,verkleinern,bearbeiten,formen", "title": "Zuschneiden", "header": "PDF zuschneiden", - "submit": "Abschicken" + "submit": "Abschicken", + "noFileSelected": "Wählen Sie eine PDF-Datei aus, um mit dem Zuschneiden zu beginnen", + "preview": { + "title": "Zuschneidebereich-Auswahl" + }, + "reset": "Auf vollständiges PDF zurücksetzen", + "coordinates": { + "title": "Position und Größe", + "x": "X-Position", + "y": "Y-Position", + "width": "Breite", + "height": "Höhe" + }, + "error": { + "invalidArea": "Zuschneidebereich überschreitet die PDF-Grenzen", + "failed": "PDF zuschneiden fehlgeschlagen" + }, + "steps": { + "selectArea": "Zuschneidebereich auswählen" + }, + "tooltip": { + "title": "PDFs zuschneiden - Anleitung", + "description": "Wählen Sie den zu beschneidenden Bereich aus Ihrer PDF durch Ziehen und Größenänderung der blauen Überlagerung auf der Miniaturansicht.", + "drag": "Ziehen Sie die Überlagerung, um den Zuschneidebereich zu verschieben", + "resize": "Ziehen Sie die Eck- und Kantengriffe, um die Größe zu ändern", + "precision": "Verwenden Sie Koordinateneingaben für präzise Positionierung" + }, + "results": { + "title": "Zuschneide-Ergebnisse" + } }, "autoSplitPDF": { "tags": "qr basiert,trennen,segment scannen,organisieren", @@ -1223,64 +2192,121 @@ "downloadJS": "Javascript herunterladen", "submit": "Anzeigen" }, - "autoRedact": { - "tags": "zensieren,schwärzen", - "title": "Automatisch zensieren/schwärzen", - "header": "Automatisch zensieren/schwärzen", - "colorLabel": "Farbe", - "textsToRedactLabel": "Zu zensierender Text (einer pro Zeile)", - "textsToRedactPlaceholder": "z.B. \\nVertraulich \\nStreng geheim", - "useRegexLabel": "Regex verwenden", - "wholeWordSearchLabel": "Ganzes Wort suchen", - "customPaddingLabel": "Zensierten Bereich vergrößern", - "convertPDFToImageLabel": "PDF in PDF-Bild konvertieren (zum Entfernen von Text hinter dem Kasten)", - "submitButton": "Zensieren" - }, "redact": { "tags": "zensieren,schwärzen,verstecken,verdunkeln,schwarz,markieren,verbergen,manuell", "title": "Manuelles Zensieren (Schwärzen)", - "header": "Manuelles Zensieren (Schwärzen)", "submit": "Zensieren", - "textBasedRedaction": "Textbasiertes Zensieren", - "pageBasedRedaction": "Seitenweises Zensieren", - "convertPDFToImageLabel": "Konvertiere PDF zu einem Bild (Zum Entfernen von Text hinter der Box verwenden)", - "pageRedactionNumbers": { - "title": "Seiten", - "placeholder": "(z.B. 1,2,8 oder 4,7,12-16 oder 2n-1)" + "error": { + "failed": "Ein Fehler ist beim Schwärzen der PDF aufgetreten." }, - "redactionColor": { - "title": "Zensurfarbe" + "modeSelector": { + "title": "Schwärzungs-Methode", + "mode": "Modus", + "automatic": "Automatisch", + "automaticDesc": "Text basierend auf Suchbegriffen schwärzen", + "manual": "Manuell", + "manualDesc": "Klicken und ziehen zum Schwärzen bestimmter Bereiche", + "manualComingSoon": "Manuelle Schwärzung kommt bald" }, - "export": "Exportieren", - "upload": "Hochladen", - "boxRedaction": "Rechteck zeichnen zum zensieren", - "zoom": "Zoom", - "zoomIn": "Vergrößern", - "zoomOut": "Verkleinern", - "nextPage": "Nächste Seite", - "previousPage": "Vorherige Seite", - "toggleSidebar": "Seitenleiste umschalten", - "showThumbnails": "Vorschau anzeigen", - "showDocumentOutline": "Dokumentübersicht anzeigen (Doppelklick zum Auf/Einklappen aller Elemente)", - "showAttatchments": "Zeige Anhänge", - "showLayers": "Ebenen anzeigen (Doppelklick, um alle Ebenen auf den Standardzustand zurückzusetzen)", - "colourPicker": "Farbauswahl", - "findCurrentOutlineItem": "Aktuell gewähltes Element finden", - "applyChanges": "Änderungen übernehmen", "auto": { + "header": "Auto-Schwärzung", "settings": { + "title": "Schwärzungs-Einstellungen", "advancedTitle": "Erweiterte Funktionen" }, + "colorLabel": "Kastenfarbe", "wordsToRedact": { - "add": "Signieren" + "title": "Zu schwärzende Wörter", + "placeholder": "Geben Sie ein Wort ein", + "add": "Hinzufügen", + "examples": "Beispiele: Vertraulich, Streng geheim" + }, + "useRegexLabel": "Regex verwenden", + "wholeWordSearchLabel": "Ganze Wörter suchen", + "customPaddingLabel": "Benutzerdefinierter zusätzlicher Abstand", + "convertPDFToImageLabel": "PDF zu PDF-Bild konvertieren" + }, + "tooltip": { + "mode": { + "header": { + "title": "Schwärzungs-Methode" + }, + "automatic": { + "title": "Automatische Schwärzung", + "text": "Findet und schwärzt automatisch angegebenen Text im gesamten Dokument. Perfekt zum Entfernen konsistenter sensibler Informationen wie Namen, Adressen oder vertrauliche Markierungen." + }, + "manual": { + "title": "Manuelle Schwärzung", + "text": "Klicken und ziehen Sie, um manuell bestimmte Bereiche zum Schwärzen auszuwählen. Gibt Ihnen präzise Kontrolle darüber, was geschwärzt wird. (Kommt bald)" + } + }, + "words": { + "header": { + "title": "Zu schwärzende Wörter" + }, + "description": { + "title": "Text-Abgleich", + "text": "Geben Sie Wörter oder Phrasen ein, um sie in Ihrem Dokument zu finden und zu schwärzen. Jedes Wort wird separat gesucht." + }, + "bullet1": "Fügen Sie jeweils ein Wort hinzu", + "bullet2": "Drücken Sie Enter oder klicken Sie auf 'Weiteres hinzufügen'", + "bullet3": "Klicken Sie auf × um Wörter zu entfernen", + "examples": { + "title": "Häufige Beispiele", + "text": "Typische zu schwärzende Wörter umfassen: Bankdaten, E-Mail-Adressen oder spezifische Namen." + } + }, + "advanced": { + "header": { + "title": "Erweiterte Schwärzungs-Einstellungen" + }, + "color": { + "title": "Rahmenfarbe & Abstand", + "text": "Passen Sie das Aussehen der Schwärzungskästen an. Schwarz ist Standard, aber Sie können jede Farbe wählen. Abstand fügt zusätzlichen Platz um den gefundenen Text hinzu." + }, + "regex": { + "title": "Regex verwenden", + "text": "Aktivieren Sie reguläre Ausdrücke für erweiterte Mustererkennung. Nützlich zum Finden von Telefonnummern, E-Mails oder komplexen Mustern.", + "bullet1": "Beispiel: \\d{4}-\\d{2}-\\d{2} um beliebige Daten im YYYY-MM-DD-Format zu finden", + "bullet2": "Mit Vorsicht verwenden - gründlich testen" + }, + "wholeWord": { + "title": "Ganze Wörter suchen", + "text": "Nur vollständige Wörter abgleichen, keine Teilübereinstimmungen. 'John' findet nicht 'Johnson', wenn aktiviert." + }, + "convert": { + "title": "In PDF-Bild konvertieren", + "text": "Konvertiert das PDF nach der Schwärzung in ein bildbasiertes PDF. Dies stellt sicher, dass Text hinter Schwärzungskästen vollständig entfernt und nicht wiederherstellbar ist." + } } }, "manual": { + "header": "Manuelle Schwärzung", + "textBasedRedaction": "Textbasierte Schwärzung", + "pageBasedRedaction": "Seitenbasierte Schwärzung", + "convertPDFToImageLabel": "PDF zu PDF-Bild konvertieren (Verwendet, um Text hinter dem Kasten zu entfernen)", "pageRedactionNumbers": { "title": "Seiten", "placeholder": "(z.B. 1,2,8 oder 4,7,12-16 oder 2n-1)" }, - "export": "Downloaden" + "redactionColor": { + "title": "Schwärzungsfarbe" + }, + "export": "Herunterladen", + "upload": "Hochladen", + "boxRedaction": "Kasten-Zeichen-Schwärzung", + "zoomIn": "Hineinzoomen", + "zoomOut": "Herauszoomen", + "nextPage": "Nächste Seite", + "previousPage": "Vorherige Seite", + "toggleSidebar": "Seitenleiste umschalten", + "showThumbnails": "Miniaturansichten anzeigen", + "showDocumentOutline": "Dokumentstruktur anzeigen (Doppelklick zum Erweitern/Reduzieren aller Elemente)", + "showAttachments": "Anhänge anzeigen", + "showLayers": "Ebenen anzeigen (Doppelklick, um alle Ebenen auf den Standardzustand zurückzusetzen)", + "colourPicker": "Farbwähler", + "findCurrentOutlineItem": "Aktuelles Gliederungselement finden", + "applyChanges": "Änderungen anwenden" } }, "tableExtraxt": { @@ -1334,9 +2360,10 @@ "tags": "stempeln,bild hinzufügen,bild zentrieren,wasserzeichen,pdf,einbetten,anpassen", "header": "PDF Stempel", "title": "PDF Stempel", + "stampSetup": "Stempel-Einstellungen", "stampType": "Stempeltyp", "stampText": "Stempeltext", - "stampImage": "Stampelbild", + "stampImage": "Stempelbild", "alphabet": "Alphabet", "fontSize": "Schriftart/Bildgröße", "rotation": "Drehung", @@ -1346,7 +2373,8 @@ "overrideY": "Y-Koordinate überschreiben", "customMargin": "Benutzerdefinierter Rand", "customColor": "Benutzerdefinierte Textfarbe", - "submit": "Abschicken" + "submit": "Abschicken", + "noStampSelected": "Kein Stempel ausgewählt. Kehren Sie zu Schritt 1 zurück." }, "removeImagePdf": { "tags": "bild entfernen,seitenoperationen,back end,server side" @@ -1391,7 +2419,7 @@ "version": "Version", "keyUsage": "Schlüsselverwendung", "selfSigned": "Selbstsigniert", - "bits": "bits" + "bits": "Bits" }, "signature": { "info": "Signaturinformationen", @@ -1425,6 +2453,8 @@ "title": "Anmelden", "header": "Anmelden", "signin": "Anmelden", + "signInWith": "Anmelden mit", + "signInAnonymously": "Als Gast anmelden", "rememberme": "Angemeldet bleiben", "invalid": "Benutzername oder Passwort ungültig.", "locked": "Ihr Konto wurde gesperrt.", @@ -1440,15 +2470,71 @@ "oauth2InvalidIdToken": "Ungültiges ID-Token", "relyingPartyRegistrationNotFound": "Keine Relying-Party-Registrierung gefunden", "userIsDisabled": "Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benutzernamen derzeit gesperrt. Bitte wenden Sie sich an den Administrator.", - "alreadyLoggedIn": "Sie sind bereits an", + "alreadyLoggedIn": "Sie sind bereits auf mehreren", "alreadyLoggedIn2": "Geräten angemeldet. Bitte melden Sie sich dort ab und versuchen es dann erneut.", "toManySessions": "Sie haben zu viele aktive Sitzungen", - "logoutMessage": "Sie wurden erfolgreich abgemeldet." + "logoutMessage": "Sie wurden erfolgreich abgemeldet.", + "youAreLoggedIn": "Sie sind angemeldet!", + "email": "E-Mail", + "password": "Passwort", + "enterEmail": "Geben Sie Ihre E-Mail-Adresse ein", + "enterPassword": "Geben Sie Ihr Passwort ein", + "loggingIn": "Anmeldung läuft...", + "signingIn": "Anmeldung läuft...", + "login": "Anmelden", + "or": "Oder", + "useMagicLink": "Stattdessen Magic Link verwenden", + "enterEmailForMagicLink": "Geben Sie Ihre E-Mail für den Magic Link ein", + "sending": "Wird gesendet…", + "sendMagicLink": "Magic Link senden", + "cancel": "Abbrechen", + "dontHaveAccount": "Sie haben noch kein Konto? Registrieren", + "home": "Startseite", + "signOut": "Abmelden", + "pleaseEnterBoth": "Bitte geben Sie sowohl E-Mail als auch Passwort ein", + "pleaseEnterEmail": "Bitte geben Sie Ihre E-Mail-Adresse ein", + "magicLinkSent": "Magic Link wurde an {{email}} gesendet! Prüfen Sie Ihre E-Mails und klicken Sie auf den Link zur Anmeldung.", + "passwordResetSent": "Passwort-Reset-Link wurde an {{email}} gesendet! Prüfen Sie Ihre E-Mails und folgen Sie den Anweisungen.", + "failedToSignIn": "Anmeldung mit {{provider}} fehlgeschlagen: {{message}}", + "unexpectedError": "Unerwarteter Fehler: {{message}}" + }, + "signup": { + "title": "Konto erstellen", + "subtitle": "Bei Stirling PDF anmelden und loslegen", + "email": "E-Mail", + "password": "Passwort", + "confirmPassword": "Passwort bestätigen", + "enterName": "Geben Sie Ihren Namen ein", + "enterEmail": "Geben Sie Ihre E-Mail-Adresse ein", + "enterPassword": "Geben Sie Ihr Passwort ein", + "confirmPasswordPlaceholder": "Passwort bestätigen", + "or": "oder", + "creatingAccount": "Konto wird erstellt...", + "signUp": "Registrieren", + "alreadyHaveAccount": "Sie haben bereits ein Konto? Anmelden", + "pleaseFillAllFields": "Bitte füllen Sie alle Felder aus", + "passwordsDoNotMatch": "Passwörter stimmen nicht überein", + "passwordTooShort": "Das Passwort muss mindestens 6 Zeichen lang sein", + "invalidEmail": "Bitte geben Sie eine gültige E-Mail-Adresse ein", + "checkEmailConfirmation": "Prüfen Sie Ihre E-Mails auf einen Bestätigungslink, um die Registrierung abzuschließen.", + "accountCreatedSuccessfully": "Konto erfolgreich erstellt! Sie können sich jetzt anmelden.", + "unexpectedError": "Unerwarteter Fehler: {{message}}" }, "pdfToSinglePage": { "title": "PDF zu einer Seite zusammenfassen", "header": "PDF zu einer Seite zusammenfassen", - "submit": "Zusammenfassen" + "submit": "Zusammenfassen", + "description": "Dieses Tool fügt alle Seiten Ihrer PDF zu einer großen einzelnen Seite zusammen. Die Breite bleibt gleich wie die ursprünglichen Seiten, aber die Höhe ist die Summe aller Seitenhöhen.", + "filenamePrefix": "einzelseite", + "files": { + "placeholder": "Wählen Sie eine PDF-Datei in der Hauptansicht aus, um zu beginnen" + }, + "error": { + "failed": "Ein Fehler ist bei der Konvertierung zu einer einzelnen Seite aufgetreten." + }, + "results": { + "title": "Einzelseiten-Ergebnisse" + } }, "pageExtracter": { "title": "Seiten extrahieren", @@ -1479,11 +2565,39 @@ }, "compress": { "title": "Komprimieren", + "desc": "PDFs komprimieren, um ihre Dateigröße zu reduzieren.", "header": "PDF komprimieren", + "method": { + "title": "Kompressions-Methode", + "quality": "Qualität", + "filesize": "Dateigröße" + }, "credit": "Dieser Dienst verwendet qpdf für die PDF-Komprimierung/-Optimierung.", "grayscale": { "label": "Graustufen für Komprimierung anwenden" }, + "tooltip": { + "header": { + "title": "Kompressions-Einstellungen - Übersicht" + }, + "description": { + "title": "Beschreibung", + "text": "Komprimierung ist eine einfache Möglichkeit, Ihre Dateigröße zu reduzieren. Wählen Sie Dateigröße, um eine Zielgröße einzugeben und wir passen die Qualität für Sie an. Wählen Sie Qualität, um die Komprimierungsstärke manuell festzulegen." + }, + "qualityAdjustment": { + "title": "Qualitätsanpassung", + "text": "Ziehen Sie den Regler, um die Kompressionsstärke anzupassen. Niedrige Werte (1-3) bewahren die Qualität, führen aber zu größeren Dateien. Höhere Werte (7-9) verkleinern die Datei stärker, reduzieren aber die Bildschärfe.", + "bullet1": "Niedrige Werte bewahren die Qualität", + "bullet2": "Höhere Werte reduzieren die Dateigröße" + }, + "grayscale": { + "title": "Graustufen", + "text": "Wählen Sie diese Option, um alle Bilder in Schwarz-Weiß zu konvertieren, was die Dateigröße erheblich reduzieren kann, insbesondere bei gescannten PDFs oder bildreichen Dokumenten." + } + }, + "error": { + "failed": "Ein Fehler ist beim Komprimieren der PDF aufgetreten." + }, "selectText": { "1": { "_value": "Kompressionseinstellungen", @@ -1493,10 +2607,7 @@ "4": "Automatischer Modus – Passt die Qualität automatisch an, um das PDF auf die exakte Größe zu bringen", "5": "Erwartete PDF-Größe (z.B. 25 MB, 10,8 MB, 25 KB)" }, - "submit": "Komprimieren", - "method": { - "filesize": "Dateigröße" - } + "submit": "Komprimieren" }, "decrypt": { "passwordPrompt": "Diese Datei ist passwortgeschützt. Bitte geben Sie das Passwort ein:", @@ -1631,6 +2742,12 @@ }, "note": "Versionshinweise sind nur auf Englisch verfügbar" }, + "swagger": { + "title": "API-Dokumentation", + "header": "API-Dokumentation", + "desc": "Stirling PDF API-Endpunkte anzeigen und testen", + "tags": "API,Dokumentation,Swagger,Endpunkte,Entwicklung" + }, "cookieBanner": { "popUp": { "title": "Wie wir Cookies verwenden", @@ -1638,8 +2755,8 @@ "1": "Wir verwenden Cookies und andere Technologien, damit Stirling PDF für Sie besser funktioniert. Dies hilft uns dabei, unsere Tools zu verbessern und weiterhin Funktionen zu entwickeln, die Ihnen gefallen werden.", "2": "Wenn Sie dies nicht möchten, klicken Sie auf „Nein, Danke“. Dadurch werden nur die unbedingt erforderlichen Cookies aktiviert, die für einen reibungslosen Ablauf erforderlich sind." }, - "acceptAllBtn": "Okay", - "acceptNecessaryBtn": "Nein Danke", + "acceptAllBtn": "OK", + "acceptNecessaryBtn": "Nein, danke", "showPreferencesBtn": "Einstellungen verwalten" }, "preferencesModal": { @@ -1668,53 +2785,286 @@ } } }, - "download": "Herunterladen", - "undo": "Rückgängig", - "convert": { - "title": "Umwandeln", - "settings": "Einstellungen", - "color": "Farbe", - "greyscale": "Graustufen", - "fillPage": "Seite füllen", - "pdfaDigitalSignatureWarning": "Das PDF enthält eine digitale Signatur. Sie wird im nächsten Schritt entfernt.", - "grayscale": "Graustufen" + "removeMetadata": { + "submit": "Metadaten entfernen" }, - "attachments": { - "tags": "einbetten, anhängen, datei, anhang, anhänge", - "title": "Anhänge hinzufügen", - "header": "Anhänge hinzufügen", - "submit": "Anhänge hinzufügen" + "sidebar": { + "toggle": "Seitenleiste umschalten" + }, + "theme": { + "toggle": "Design umschalten" + }, + "view": { + "viewer": "Betrachter", + "pageEditor": "Seiteneditor", + "fileManager": "Dateiverwaltung" + }, + "pageEditor": { + "title": "Seiten-Editor", + "save": "Änderungen speichern", + "noPdfLoaded": "Keine PDF geladen. Bitte laden Sie eine PDF zum Bearbeiten hoch.", + "rotatedLeft": "Nach links gedreht:", + "rotatedRight": "Nach rechts gedreht:", + "deleted": "Gelöscht:", + "movedLeft": "Nach links verschoben:", + "movedRight": "Nach rechts verschoben:", + "splitAt": "Geteilt bei:", + "insertedPageBreak": "Seitenumbruch eingefügt bei:", + "addFileNotImplemented": "Datei hinzufügen in der Demo nicht implementiert", + "closePdf": "PDF schließen", + "reset": "Änderungen zurücksetzen", + "zoomIn": "Vergrößern", + "zoomOut": "Verkleinern", + "fitToWidth": "An Breite anpassen", + "actualSize": "Originalgröße" + }, + "viewer": { + "firstPage": "Erste Seite", + "lastPage": "Letzte Seite", + "previousPage": "Vorherige Seite", + "nextPage": "Nächste Seite", + "zoomIn": "Vergrößern", + "zoomOut": "Verkleinern", + "singlePageView": "Einzelseitenansicht", + "dualPageView": "Doppelseitenansicht" }, "rightRail": { + "closeSelected": "Ausgewählte Dateien schließen", "selectAll": "Alle auswählen", - "deselectAll": "Auswahl aufheben" + "deselectAll": "Auswahl aufheben", + "selectByNumber": "Nach Seitenzahlen auswählen", + "deleteSelected": "Ausgewählte Seiten löschen", + "closePdf": "PDF schließen", + "exportAll": "PDF exportieren", + "downloadSelected": "Ausgewählte Dateien herunterladen", + "downloadAll": "Alle herunterladen", + "toggleTheme": "Design wechseln", + "language": "Sprache", + "search": "PDF durchsuchen", + "panMode": "Verschiebemodus", + "rotateLeft": "Nach links drehen", + "rotateRight": "Nach rechts drehen", + "toggleSidebar": "Seitenleiste umschalten" + }, + "search": { + "title": "PDF durchsuchen", + "placeholder": "Suchbegriff eingeben..." + }, + "guestBanner": { + "title": "Sie verwenden Stirling PDF als Gast!", + "message": "Erstellen Sie ein kostenloses Konto, um Ihre Arbeit zu speichern, auf mehr Funktionen zuzugreifen und das Projekt zu unterstützen.", + "dismiss": "Banner schließen", + "signUp": "Kostenlos registrieren" + }, + "toolPicker": { + "searchPlaceholder": "Werkzeuge suchen...", + "noToolsFound": "Keine Werkzeuge gefunden", + "allTools": "ALLE WERKZEUGE", + "quickAccess": "SCHNELLZUGRIFF", + "categories": { + "standardTools": "Standard-Werkzeuge", + "advancedTools": "Erweiterte Werkzeuge", + "recommendedTools": "Empfohlene Werkzeuge" + }, + "subcategories": { + "signing": "Signierung", + "documentSecurity": "Dokumentensicherheit", + "verification": "Verifizierung", + "documentReview": "Dokumentenprüfung", + "pageFormatting": "Seitenformatierung", + "extraction": "Extraktion", + "removal": "Entfernung", + "automation": "Automatisierung", + "general": "Allgemein", + "advancedFormatting": "Erweiterte Formatierung", + "developerTools": "Entwicklerwerkzeuge" + } }, "quickAccess": { - "sign": "Signieren" + "read": "Lesen", + "sign": "Signieren", + "automate": "Automatisieren", + "files": "Dateien", + "activity": "Aktivität", + "config": "Konfiguration", + "allTools": "Alle Werkzeuge" }, "fileUpload": { + "selectFile": "Datei auswählen", + "selectFiles": "Dateien auswählen", + "selectPdfToView": "PDF zum Anzeigen auswählen", + "selectPdfToEdit": "PDF zum Bearbeiten auswählen", + "chooseFromStorage": "Datei aus dem Speicher wählen oder neue PDF hochladen", + "chooseFromStorageMultiple": "Dateien aus dem Speicher wählen oder neue PDFs hochladen", + "loadFromStorage": "Aus Speicher laden", + "filesAvailable": "Dateien verfügbar", "loading": "Laden...", - "or": "oder" + "or": "oder", + "dropFileHere": "Datei hier ablegen oder zum Hochladen klicken", + "dropFilesHere": "Dateien hier ablegen oder Upload-Button klicken", + "pdfFilesOnly": "Nur PDF-Dateien", + "supportedFileTypes": "Unterstützte Dateitypen", + "upload": "Hochladen", + "uploadFile": "Datei hochladen", + "uploadFiles": "Dateien hochladen", + "noFilesInStorage": "Keine Dateien im Speicher verfügbar. Laden Sie zuerst einige Dateien hoch.", + "selectFromStorage": "Aus Speicher auswählen", + "backToTools": "Zurück zu Tools", + "addFiles": "Dateien hinzufügen", + "dragFilesInOrClick": "Dateien hineinziehen oder \"Dateien hinzufügen\" klicken zum Durchsuchen" }, "fileManager": { + "title": "PDF-Dateien hochladen", + "subtitle": "Dateien zum Speicher hinzufügen für einfachen Zugriff in allen Tools", + "filesSelected": "Dateien ausgewählt", + "clearSelection": "Auswahl aufheben", + "openInFileEditor": "In Dateieditor öffnen", + "uploadError": "Einige Dateien konnten nicht hochgeladen werden.", + "failedToOpen": "Datei konnte nicht geöffnet werden. Sie wurde möglicherweise aus dem Speicher entfernt.", + "failedToLoad": "Datei konnte nicht zum aktiven Satz hinzugefügt werden.", + "storageCleared": "Browser hat Speicher geleert. Dateien wurden entfernt. Bitte laden Sie sie erneut hoch.", + "clearAll": "Alles löschen", + "reloadFiles": "Dateien neu laden", + "dragDrop": "Dateien hier per Drag & Drop ablegen", + "clickToUpload": "Klicken zum Hochladen von Dateien", + "selectedFiles": "Ausgewählte Dateien", + "storage": "Speicher", + "filesStored": "Dateien gespeichert", + "storageError": "Speicherfehler aufgetreten", + "storageLow": "Der Speicherplatz wird knapp. Erwägen Sie das Entfernen alter Dateien.", + "supportMessage": "Basiert auf Browser-Datenbankspeicher für unbegrenzte Kapazität", + "noFileSelected": "Keine Dateien ausgewählt", + "showHistory": "Verlauf anzeigen", + "hideHistory": "Verlauf ausblenden", + "fileHistory": "Dateiverlauf", + "loadingHistory": "Lade Verlauf...", + "lastModified": "Zuletzt geändert", + "toolChain": "Angewendete Werkzeuge", + "restore": "Wiederherstellen", + "searchFiles": "Dateien suchen...", + "recent": "Kürzlich", + "localFiles": "Lokale Dateien", + "myFiles": "Meine Dateien", + "noRecentFiles": "Keine kürzlichen Dateien gefunden", + "dropFilesHint": "Dateien hier ablegen zum Hochladen", + "googleDriveNotAvailable": "Google Drive-Integration nicht verfügbar", + "openFiles": "Dateien öffnen", + "openFile": "Datei öffnen", + "details": "Dateidetails", + "fileSize": "Größe", + "totalSelected": "Gesamt ausgewählt", + "dropFilesHere": "Dateien hier ablegen", "selectAll": "Alle auswählen", "deselectAll": "Auswahl aufheben", "deleteSelected": "Auswahl löschen", + "downloadSelected": "Ausgewählte herunterladen", + "selectedCount": "{{count}} ausgewählt", "download": "Herunterladen", - "delete": "Löschen" + "delete": "Löschen", + "unsupported": "Nicht unterstützt" + }, + "storage": { + "temporaryNotice": "Dateien werden temporär in Ihrem Browser gespeichert und können automatisch gelöscht werden", + "storageLimit": "Speicherlimit", + "storageUsed": "Temporärer Speicher verwendet", + "storageFull": "Der Speicher ist fast voll. Erwägen Sie das Entfernen einiger Dateien.", + "fileTooLarge": "Datei zu groß. Maximale Größe pro Datei ist", + "storageQuotaExceeded": "Speicherkontingent überschritten. Bitte entfernen Sie einige Dateien, bevor Sie weitere hochladen.", + "approximateSize": "Ungefähre Größe" }, "sanitize": { + "title": "Bereinigen", + "desc": "Potentiell schädliche Elemente aus PDF-Dateien entfernen.", "submit": "PDF Bereinigen", + "completed": "Bereinigung erfolgreich abgeschlossen", + "error.generic": "Bereinigung fehlgeschlagen", + "error.failed": "Ein Fehler ist bei der Bereinigung der PDF aufgetreten.", + "filenamePrefix": "bereinigt", + "sanitizationResults": "Bereinigungsergebnisse", "steps": { - "settings": "Einstellungen" + "files": "Dateien", + "settings": "Einstellungen", + "results": "Ergebnisse" + }, + "files": { + "placeholder": "Wählen Sie eine PDF-Datei in der Hauptansicht aus, um zu beginnen" + }, + "options": { + "title": "Bereinigungs-Optionen", + "note": "Wählen Sie die Elemente aus, die Sie aus der PDF entfernen möchten. Mindestens eine Option muss ausgewählt werden.", + "removeJavaScript": "JavaScript entfernen", + "removeEmbeddedFiles": "Eingebettete Dateien entfernen", + "removeXMPMetadata": "XMP-Metadaten entfernen", + "removeMetadata": "Dokument-Metadaten entfernen", + "removeLinks": "Links entfernen", + "removeFonts": "Schriftarten entfernen" + } + }, + "addPassword": { + "title": "Passwort hinzufügen", + "desc": "Ihr PDF-Dokument mit einem Passwort verschlüsseln.", + "completed": "Passwortschutz angewendet", + "submit": "Verschlüsseln", + "filenamePrefix": "verschluesselt", + "error": { + "failed": "Ein Fehler ist bei der Verschlüsselung der PDF aufgetreten." + }, + "passwords": { + "stepTitle": "Passwörter & Verschlüsselung", + "completed": "Passwörter konfiguriert", + "user": { + "label": "Benutzerpasswort", + "placeholder": "Benutzerpasswort eingeben" + }, + "owner": { + "label": "Eigentümerpasswort", + "placeholder": "Eigentümerpasswort eingeben" + } + }, + "encryption": { + "keyLength": { + "label": "Verschlüsselungsschlüssellänge", + "40bit": "40-bit (Niedrig)", + "256bit": "256-bit (Hoch)" + } + }, + "results": { + "title": "Verschlüsselte PDFs" + }, + "tooltip": { + "header": { + "title": "Passwort-Schutz - Übersicht" + }, + "passwords": { + "title": "Passwort-Typen", + "text": "Benutzerpasswörter beschränken das Öffnen des Dokuments, während Eigentümerpasswörter kontrollieren, was mit dem Dokument nach dem Öffnen gemacht werden kann. Sie können beide oder nur eines festlegen.", + "bullet1": "Benutzerpasswort: Erforderlich zum Öffnen der PDF", + "bullet2": "Eigentümerpasswort: Kontrolliert Dokumentberechtigungen (wird nicht von allen PDF-Betrachtern unterstützt)" + }, + "encryption": { + "title": "Verschlüsselungsstärken", + "text": "Höhere Verschlüsselungsebenen bieten bessere Sicherheit, werden aber möglicherweise nicht von älteren PDF-Betrachtern unterstützt.", + "bullet1": "40-bit: Grundlegende Sicherheit, kompatibel mit älteren Betrachtern", + "bullet2": "128-bit: Standard-Sicherheit, weit verbreitet unterstützt", + "bullet3": "256-bit: Maximale Sicherheit, erfordert moderne Betrachter" + }, + "permissions": { + "title": "Berechtigungen ändern", + "text": "Diese Berechtigungen kontrollieren, was Benutzer mit der PDF machen können. Am effektivsten in Kombination mit einem Eigentümerpasswort." + } } }, "changePermissions": { "title": "Berechtigungen ändern", + "desc": "Dokumentbeschränkungen und -berechtigungen ändern.", + "completed": "Berechtigungen geändert", "submit": "Berechtigungen ändern", + "error": { + "failed": "Ein Fehler ist beim Ändern der PDF-Berechtigungen aufgetreten." + }, "permissions": { "preventAssembly": { - "label": "Das zusammensetzen des PDFs verhindern" + "label": "Das Zusammenstellen des PDFs verhindern" }, "preventExtractContent": { "label": "Inhaltsextrahierung verhindern" @@ -1735,13 +3085,184 @@ "label": "Drucken verhindern" }, "preventPrintingFaithful": { - "label": "Drucken verschiedener Formate verhindern" + "label": "Degradiertes Drucken verhindern" } }, + "results": { + "title": "Modifizierte PDFs" + }, "tooltip": { "header": { "title": "Berechtigungen ändern" + }, + "description": { + "text": "Ändert Dokumentberechtigungen und erlaubt/verweigert Zugriff auf verschiedene Funktionen in PDF-Readern." + }, + "warning": { + "text": "Um diese Berechtigungen unveränderlich zu machen, verwenden Sie das Passwort hinzufügen-Tool, um ein Besitzer-Passwort zu setzen." } } + }, + "removePassword": { + "title": "Passwort entfernen", + "desc": "Den Passwortschutz eines PDFs entfernen", + "tags": "sichern,entschlüsseln,sicherheit,passwort aufheben,passwort löschen", + "password": { + "stepTitle": "Passwort entfernen", + "label": "Aktuelles Passwort", + "placeholder": "Aktuelles Passwort eingeben", + "completed": "Passwort konfiguriert" + }, + "filenamePrefix": "entschluesselt", + "error": { + "failed": "Ein Fehler ist beim Entfernen des Passworts von der PDF aufgetreten." + }, + "tooltip": { + "description": "Das Entfernen des Passwortschutzes erfordert das Passwort, das zur Verschlüsselung der PDF verwendet wurde. Dies entschlüsselt das Dokument und macht es ohne Passwort zugänglich." + }, + "submit": "Entfernen", + "results": { + "title": "Entschlüsselte PDFs" + } + }, + "automate": { + "title": "Automatisieren", + "desc": "Mehrstufige Arbeitsabläufe durch Verkettung von PDF-Aktionen erstellen. Ideal für wiederkehrende Aufgaben.", + "invalidStep": "Ungültiger Schritt", + "files": { + "placeholder": "Wählen Sie Dateien aus, die mit dieser Automatisierung verarbeitet werden sollen" + }, + "selection": { + "title": "Automatisierungs-Auswahl", + "saved": { + "title": "Gespeichert" + }, + "createNew": { + "title": "Neue Automatisierung erstellen" + }, + "suggested": { + "title": "Vorschläge" + } + }, + "creation": { + "createTitle": "Automatisierung erstellen", + "editTitle": "Automatisierung bearbeiten", + "intro": "Automatisierungen führen Werkzeuge sequenziell aus. Fügen Sie Werkzeuge in der gewünschten Reihenfolge hinzu, um zu beginnen.", + "name": { + "label": "Name der Automatisierung", + "placeholder": "Meine Automatisierung" + }, + "description": { + "label": "Beschreibung (optional)", + "placeholder": "Beschreiben Sie, was diese Automatisierung macht..." + }, + "tools": { + "selectTool": "Werkzeug auswählen...", + "selected": "Ausgewählte Werkzeuge", + "remove": "Werkzeug entfernen", + "configure": "Werkzeug konfigurieren", + "notConfigured": "! Nicht konfiguriert", + "addTool": "Werkzeug hinzufügen", + "add": "Werkzeug hinzufügen..." + }, + "save": "Automatisierung speichern", + "unsavedChanges": { + "title": "Ungespeicherte Änderungen", + "message": "Sie haben ungespeicherte Änderungen. Sind Sie sicher, dass Sie zurückgehen möchten? Alle Änderungen gehen verloren.", + "cancel": "Abbrechen", + "confirm": "Zurückgehen" + }, + "icon": { + "label": "Symbol" + } + }, + "run": { + "title": "Automatisierung ausführen" + }, + "sequence": { + "unnamed": "Unbenannte Automatisierung", + "steps": "{{count}} Schritte", + "running": "Automatisierung läuft...", + "run": "Automatisierung ausführen", + "finish": "Fertigstellen" + }, + "reviewTitle": "Automatisierungsergebnisse", + "config": { + "loading": "Werkzeugkonfiguration wird geladen...", + "noSettings": "Dieses Werkzeug hat keine konfigurierbaren Einstellungen.", + "title": "{{toolName}} konfigurieren", + "description": "Einstellungen für dieses Tool konfigurieren. Diese Einstellungen werden angewendet, wenn die Automatisierung läuft.", + "cancel": "Abbrechen", + "save": "Konfiguration speichern" + }, + "copyToSaved": "In gespeicherte kopieren" + }, + "automation": { + "suggested": { + "securePdfIngestion": "Sichere PDF-Eingabe", + "securePdfIngestionDesc": "Umfassender PDF-Verarbeitungsworkflow, der Dokumente bereinigt, OCR mit Säuberung anwendet, in PDF/A-Format für Langzeitarchivierung konvertiert und die Dateigröße optimiert.", + "emailPreparation": "E-Mail-Vorbereitung", + "emailPreparationDesc": "Optimiert PDFs für E-Mail-Verteilung durch Komprimierung von Dateien, Aufteilen großer Dokumente in 20MB-Blöcke für E-Mail-Kompatibilität und Entfernen von Metadaten für den Datenschutz.", + "secureWorkflow": "Sicherheits-Workflow", + "secureWorkflowDesc": "Sichert PDF-Dokumente durch Entfernen potentiell schädlicher Inhalte wie JavaScript und eingebettete Dateien, dann fügt Passwortschutz hinzu, um unbefugten Zugriff zu verhindern. Passwort ist standardmäßig auf 'password' gesetzt.", + "processImages": "Bilder verarbeiten", + "processImagesDesc": "Konvertiert mehrere Bilddateien in ein einzelnes PDF-Dokument und wendet dann OCR-Technologie an, um durchsuchbaren Text aus den Bildern zu extrahieren." + } + }, + "common": { + "copy": "Kopieren", + "copied": "Kopiert!", + "refresh": "Aktualisieren", + "retry": "Wiederholen", + "remaining": "verbleibend", + "used": "verwendet", + "available": "verfügbar", + "cancel": "Abbrechen" + }, + "config": { + "account": { + "overview": { + "title": "Kontoeinstellungen", + "manageAccountPreferences": "Verwalten Sie Ihre Kontoeinstellungen", + "guestDescription": "Sie sind als Gast angemeldet. Erwägen Sie ein Upgrade Ihres Kontos oben." + }, + "upgrade": { + "title": "Gastkonto upgraden", + "description": "Verknüpfen Sie Ihr Konto, um Ihre Historie zu bewahren und mehr Funktionen zu nutzen!", + "socialLogin": "Mit Social-Media-Konto upgraden", + "linkWith": "Verknüpfen mit", + "emailPassword": "oder geben Sie Ihre E-Mail und Ihr Passwort ein", + "email": "E-Mail", + "emailPlaceholder": "Geben Sie Ihre E-Mail-Adresse ein", + "password": "Passwort (optional)", + "passwordPlaceholder": "Passwort festlegen", + "passwordNote": "Leer lassen, um nur E-Mail-Verifizierung zu verwenden", + "upgradeButton": "Konto upgraden" + } + }, + "apiKeys": { + "description": "Ihr API-Schlüssel für den Zugriff auf Stirlings PDF-Tools. Kopieren Sie ihn in Ihr Projekt oder aktualisieren Sie, um einen neuen zu generieren.", + "publicKeyAriaLabel": "Öffentlicher API-Schlüssel", + "copyKeyAriaLabel": "API-Schlüssel kopieren", + "refreshAriaLabel": "API-Schlüssel aktualisieren", + "includedCredits": "Enthaltene Credits", + "purchasedCredits": "Gekaufte Credits", + "totalCredits": "Credits insgesamt", + "chartAriaLabel": "Credit-Verbrauch: enthalten {{includedUsed}} von {{includedTotal}}, gekauft {{purchasedUsed}} von {{purchasedTotal}}", + "nextReset": "Nächster Reset", + "lastApiUse": "Letzte API-Nutzung", + "overlayMessage": "Erstellen Sie einen Schlüssel, um Credits und verfügbare Credits zu sehen", + "label": "API-Schlüssel", + "guestInfo": "Gastnutzer erhalten keine API-Schlüssel. Erstellen Sie ein Konto, um einen API-Schlüssel für Ihre Anwendungen zu erhalten.", + "goToAccount": "Zu Konto wechseln", + "refreshModal": { + "title": "API-Schlüssel aktualisieren", + "warning": "⚠️ Warnung: Diese Aktion wird neue API-Schlüssel generieren und Ihre bisherigen Schlüssel ungültig machen.", + "impact": "Alle Anwendungen oder Dienste, die derzeit diese Schlüssel verwenden, funktionieren nicht mehr, bis Sie sie mit den neuen Schlüsseln aktualisieren.", + "confirmPrompt": "Sind Sie sicher, dass Sie fortfahren möchten?", + "confirmCta": "Schlüssel aktualisieren" + }, + "generateError": "Wir konnten Ihren API-Schlüssel nicht generieren." + } } } \ No newline at end of file diff --git a/frontend/public/locales/it-IT/translation.json b/frontend/public/locales/it-IT/translation.json index 730c4f59a..b95b1ca8f 100644 --- a/frontend/public/locales/it-IT/translation.json +++ b/frontend/public/locales/it-IT/translation.json @@ -35,17 +35,35 @@ "true": "Vero", "false": "Falso", "unknown": "Sconosciuto", + "app": { + "description": "L’alternativa gratuita ad Adobe Acrobat (10M+ download)" + }, "save": "Salva", "saveToBrowser": "Salva nel browser", + "download": "Salva", + "undoOperationTooltip": "Clicca per annullare l’ultima operazione e ripristinare i file originali", + "undo": "Annulla", + "moreOptions": "Altre opzioni", + "editYourNewFiles": "Modifica il/i nuovo/i file", "close": "Chiudi", + "fileSelected": "Selezionato: {{filename}}", + "chooseFile": "Scegli file", "filesSelected": "file selezionati", + "files": { + "title": "File", + "upload": "Carica", + "uploadFiles": "Carica file", + "addFiles": "Aggiungi file", + "selectFromWorkbench": "Seleziona file dal banco di lavoro oppure ", + "selectMultipleFromWorkbench": "Seleziona almeno {{count}} file dal banco di lavoro oppure " + }, "noFavourites": "Nessun preferito", "downloadComplete": "Download completo", "bored": "Stanco di aspettare?", "alphabet": "Alfabeto", "downloadPdf": "Scarica PDF", "text": "Testo", - "font": "Font", + "font": "Carattere", "selectFillter": "-- Seleziona --", "pageNum": "Numero pagina", "sizes": { @@ -71,6 +89,10 @@ "githubSubmit": "GitHub: apri un ticket", "discordSubmit": "Discord: invia post di supporto" }, + "warning": { + "tooltipTitle": "Avviso" + }, + "edit": "Modifica", "delete": "Elimina", "username": "Nome utente", "password": "Password", @@ -82,6 +104,7 @@ "green": "Verde", "blue": "Blu", "custom": "Personalizzato", + "comingSoon": "In arrivo", "WorkInProgess": "Lavori in corso, potrebbe non funzionare o essere difettoso, segnalare eventuali problemi!", "poweredBy": "Alimentato da", "yes": "Si", @@ -101,7 +124,7 @@ "downgradeCurrentUserLongMessage": "Impossibile declassare il ruolo dell'utente corrente. Pertanto, l'utente corrente non verrà visualizzato.", "userAlreadyExistsOAuthMessage": "L'utente esiste già come utente OAuth2.", "userAlreadyExistsWebMessage": "L'utente esiste già come utente web.", - "oops": "Oops!", + "oops": "Ops!", "help": "Aiuto", "goHomepage": "Vai alla Homepage", "joinDiscord": "Unisciti al nostro server Discord", @@ -115,12 +138,14 @@ "page": "Pagina", "pages": "Pagine", "loading": "Caricamento...", + "review": "Revisione", "addToDoc": "Aggiungi al documento", "reset": "Resetta", "apply": "Applica", "noFileSelected": "Nessun file selezionato. Caricane uno.", "legal": { "privacy": "Informativa sulla privacy", + "iAgreeToThe": "Accetto tutti i", "terms": "Termini e Condizioni", "accessibility": "Accessibilità", "cookie": "Informativa sui cookie", @@ -286,7 +311,7 @@ "loading": "Caricamento...", "failedToLoad": "Impossibile caricare i dati dell'endpoint. Prova ad aggiornare.", "home": "Home", - "login": "Login", + "login": "Accesso", "top": "Migliore", "numberOfVisits": "Numero di visite", "visitsTooltip": "Visite: {0} ({1}% del totale)", @@ -347,13 +372,9 @@ "title": "Ruota", "desc": "Ruota un PDF." }, - "imageToPDF": { - "title": "Da immagine a PDF", - "desc": "Converti un'immagine (PNG, JPEG, GIF) in PDF." - }, - "pdfToImage": { - "title": "Da PDF a immagine", - "desc": "Converti un PDF in un'immagine. (PNG, JPEG, GIF)" + "convert": { + "title": "Converti", + "desc": "Converti file tra diversi formati" }, "pdfOrganiser": { "title": "Organizza", @@ -363,22 +384,14 @@ "title": "Aggiungi Immagine", "desc": "Aggiungi un'immagine in un punto specifico del PDF (Lavori in corso)" }, + "addAttachments": { + "title": "Aggiungi allegati", + "desc": "Aggiungi o rimuovi file incorporati (allegati) da/verso un PDF" + }, "watermark": { "title": "Aggiungi Filigrana", "desc": "Aggiungi una filigrana al tuo PDF." }, - "permissions": { - "title": "Cambia Permessi", - "desc": "Cambia i permessi del tuo PDF." - }, - "pageRemover": { - "title": "Rimuovi", - "desc": "Elimina alcune pagine dal PDF." - }, - "addPassword": { - "title": "Aggiungi Password", - "desc": "Crittografa il tuo PDF con una password." - }, "removePassword": { "title": "Rimuovi Password", "desc": "Rimuovi la password dal tuo PDF." @@ -395,10 +408,6 @@ "title": "Modifica Proprietà", "desc": "Modifica/Aggiungi/Rimuovi le proprietà di un documento PDF." }, - "fileToPDF": { - "title": "Converti file in PDF", - "desc": "Converti quasi ogni file in PDF (DOCX, PNG, XLS, PPT, TXT e altro)" - }, "ocr": { "title": "OCR / Pulisci scansioni", "desc": "Pulisci scansioni ed estrai testo da immagini, convertendo le immagini in testo puro." @@ -407,33 +416,9 @@ "title": "Estrai immagini", "desc": "Estrai tutte le immagini da un PDF e salvale come zip." }, - "pdfToPDFA": { - "title": "Converti in PDF/A", - "desc": "Converti un PDF nel formato PDF/A per archiviazione a lungo termine." - }, - "PDFToWord": { - "title": "Da PDF a Word", - "desc": "Converti un PDF nei formati Word (DOC, DOCX e ODT)" - }, - "PDFToPresentation": { - "title": "Da PDF a presentazioni", - "desc": "Converti un PDF in presentazioni (PPT, PPTX and ODP)" - }, - "PDFToText": { - "title": "Da PDF a testo/RTF", - "desc": "Converti un PDF in testo o RTF." - }, - "PDFToHTML": { - "title": "Da PDF ad HTML", - "desc": "Converti un PDF in HTML." - }, - "PDFToXML": { - "title": "Da PDF a XML", - "desc": "Converti un PDF in XML." - }, - "ScannerImageSplit": { - "title": "Trova/Dividi foto scansionate", - "desc": "Estrai più foto da una singola foto o PDF." + "scannerImageSplit": { + "title": "Rileva/Dividi foto scansionate", + "desc": "Divide più foto all’interno di una foto/PDF" }, "sign": { "title": "Firma", @@ -443,6 +428,10 @@ "title": "Appiattisci", "desc": "Rimuovi tutti gli elementi interattivi e moduli da un PDF." }, + "certSign": { + "title": "Firma con certificato", + "desc": "Firma un PDF con un certificato/chiave (PEM/P12)" + }, "repair": { "title": "Ripara", "desc": "Prova a riparare un PDF corrotto." @@ -459,10 +448,6 @@ "title": "Compara", "desc": "Vedi e compara le differenze tra due PDF." }, - "certSign": { - "title": "Firma con certificato", - "desc": "Firma un PDF con un certificato/chiave (PEM/P12)" - }, "removeCertSign": { "title": "Rimuovere firma dal certificato", "desc": "Rimuovi la firma del certificato dal PDF" @@ -471,21 +456,21 @@ "title": "Layout multipagina", "desc": "Unisci più pagine di un documento PDF in un'unica pagina" }, + "bookletImposition": { + "title": "Imposizione a libretto", + "desc": "Crea libretti con corretto ordinamento pagine e layout multipagina per stampa e rilegatura" + }, "scalePages": { "title": "Regola le dimensioni/scala della pagina", "desc": "Modificare le dimensioni/scala della pagina e/o dei suoi contenuti." }, - "pipeline": { - "title": "Pipeline", - "desc": "Esegui più azioni sui PDF definendo script di pipeline" - }, "addPageNumbers": { "title": "Aggiungi numeri di pagina", "desc": "Aggiungi numeri di pagina in tutto un documento in una posizione prestabilita" }, - "auto-rename": { - "title": "Rinomina automaticamente il file PDF", - "desc": "Rinomina automaticamente un file PDF in base all'intestazione rilevata" + "autoRename": { + "title": "Rinomina automatica file PDF", + "desc": "Rinomina automaticamente un file PDF in base all’intestazione rilevata" }, "adjustContrast": { "title": "Regola colori/contrasto", @@ -499,34 +484,14 @@ "title": "Pagine divise automaticamente", "desc": "Dividi automaticamente il PDF scansionato con il codice QR dello divisore di pagina fisico scansionato" }, - "sanitizePDF": { - "title": "Pulire", - "desc": "Rimuovi script e altri elementi dai file PDF" - }, - "URLToPDF": { - "title": "URL/sito Web in PDF", - "desc": "Converte qualsiasi URL http(s) in PDF" - }, - "HTMLToPDF": { - "title": "Da HTML a PDF", - "desc": "Converte qualsiasi file HTML o zip in PDF" - }, - "MarkdownToPDF": { - "title": "Markdown in PDF", - "desc": "Converte qualsiasi file Markdown in PDF" - }, - "PDFToMarkdown": { - "title": "PDF in Markdown", - "desc": "Converte qualsiasi PDF in Markdown" + "sanitize": { + "title": "Sanitizza", + "desc": "Rimuovi elementi potenzialmente dannosi dai PDF" }, "getPdfInfo": { "title": "Ottieni TUTTE le informazioni in PDF", "desc": "Raccogli tutte le informazioni possibili sui PDF" }, - "pageExtracter": { - "title": "Estrai pagina/e", - "desc": "Estrae le pagine selezionate dal PDF" - }, "pdfToSinglePage": { "title": "PDF in un'unica pagina di grandi dimensioni", "desc": "Unisce tutte le pagine PDF in un'unica grande pagina" @@ -535,33 +500,21 @@ "title": "Mostra Javascript", "desc": "Cerca e visualizza qualsiasi JS inserito in un PDF" }, - "autoRedact": { - "title": "Redazione automatica", - "desc": "Redige automaticamente (oscura) il testo in un PDF in base al testo immesso" - }, "redact": { "title": "Redazione manuale", "desc": "Redige un PDF in base al testo selezionato, alle forme disegnate e/o alle pagina selezionata(e)" }, - "PDFToCSV": { - "title": "Da PDF a CSV", - "desc": "Estrae tabelle da un PDF convertendolo in CSV" + "overlayPdfs": { + "title": "Sovrapponi PDF", + "desc": "Sovrapponi PDF sopra un altro PDF" }, - "split-by-size-or-count": { - "title": "Divisione automatica per dimensione/numero", - "desc": "Dividi un singolo PDF in più documenti in base alle dimensioni, al numero di pagine o al numero di documenti" - }, - "overlay-pdfs": { - "title": "Sovrapposizione di PDF", - "desc": "Sovrappone i PDF sopra un altro PDF" - }, - "split-by-sections": { + "splitBySections": { "title": "Dividi PDF per sezioni", - "desc": "Dividi ciascuna pagina di un PDF in sezioni orizzontali e verticali più piccole" + "desc": "Divide ogni pagina di un PDF in sezioni orizzontali e verticali più piccole" }, - "AddStampRequest": { + "addStamp": { "title": "Aggiungi timbro al PDF", - "desc": "Aggiungi testo o aggiungi timbri immagine nelle posizioni prestabilite" + "desc": "Aggiungi timbri di testo o immagine in posizioni specifiche" }, "removeImage": { "title": "Rimuovi immagine", @@ -575,48 +528,79 @@ "title": "Convalida la firma PDF", "desc": "Verificare le firme digitali e i certificati nei documenti PDF" }, - "replace-color": { - "title": "Sostituisci e inverti il colore", - "desc": "Sostituisci il colore del testo e dello sfondo nel PDF e inverti il ​​colore completo del PDF per ridurre le dimensioni del file" + "swagger": { + "title": "Documentazione API", + "desc": "Visualizza documentazione API e testa gli endpoint" }, - "convert": { - "title": "Converti" - }, - "attachments": { - "title": "Aggiungi allegati", - "desc": "Aggiungere o rimuovere file incorporati (allegati) da/a un PDF" + "fakeScan": { + "title": "Finta scansione", + "desc": "Crea un PDF che sembri scansionato" }, "editTableOfContents": { "title": "Modifica indice", "desc": "Aggiungi o modifica segnalibri e sommario nei documenti PDF" }, + "manageCertificates": { + "title": "Gestisci certificati", + "desc": "Importa, esporta o elimina i file certificato usati per firmare i PDF." + }, + "read": { + "title": "Leggi", + "desc": "Visualizza e annota PDF. Evidenzia testo, disegna o inserisci commenti per revisione e collaborazione." + }, + "reorganizePages": { + "title": "Riorganizza pagine", + "desc": "Riorganizza, duplica o elimina pagine PDF con controllo visivo drag‑and‑drop." + }, "extractPages": { - "title": "Estrai pagine" + "title": "Estrai pagine", + "desc": "Estrai pagine specifiche da un PDF" }, "removePages": { "title": "Rimuovi", "desc": "Elimina alcune pagine dal PDF." }, - "removeImagePdf": { - "title": "Rimuovi immagine", - "desc": "Rimuovi le immagini dal PDF per ridurre la dimensione del file" - }, "autoSizeSplitPDF": { "title": "Divisione automatica per dimensione/numero", "desc": "Dividi un singolo PDF in più documenti in base alle dimensioni, al numero di pagine o al numero di documenti" }, - "adjust-contrast": { - "title": "Regola colori/contrasto", - "desc": "Regola contrasto, saturazione e luminosità di un PDF" - }, "replaceColorPdf": { "title": "Sostituisci e inverti il colore", "desc": "Sostituisci il colore del testo e dello sfondo nel PDF e inverti il ​​colore completo del PDF per ridurre le dimensioni del file" }, + "devApi": { + "title": "API", + "desc": "Link alla documentazione API" + }, + "devFolderScanning": { + "title": "Scansione cartelle automatizzata", + "desc": "Link alla guida per scansione cartelle automatizzata" + }, + "devSsoGuide": { + "title": "Guida SSO", + "desc": "Link alla guida SSO" + }, + "devAirgapped": { + "title": "Setup isolato (air‑gapped)", + "desc": "Link alla guida per setup air‑gapped" + }, + "addPassword": { + "title": "Aggiungi Password", + "desc": "Crittografa il tuo PDF con una password." + }, "changePermissions": { - "title": "Cambia Permessi" + "title": "Cambia Permessi", + "desc": "Modifica restrizioni e permessi del documento" + }, + "automate": { + "title": "Automatizza", + "desc": "Crea flussi multi‑step concatenando azioni PDF. Ideale per attività ricorrenti." } }, + "landing": { + "addFiles": "Aggiungi file", + "uploadFromComputer": "Carica dal computer" + }, "viewPdf": { "tags": "visualizzare,leggere,annotare,testo,immagine", "title": "Visualizza/Modifica PDF", @@ -650,13 +634,26 @@ "merge": { "tags": "unione,operazioni sulla pagina,back-end,lato server", "title": "Unisci", - "header": "Unisci 2 o più PDF", - "sortByName": "Ordina per nome", - "sortByDate": "Ordina per data", - "removeCertSign": "Rimuovere la firma digitale nel file unito?", + "removeDigitalSignature.tooltip": { + "title": "Rimuovi firma digitale", + "description": "Le firme digitali verranno invalidate durante l’unione dei file. Seleziona per rimuoverle dal PDF finale." + }, + "generateTableOfContents.tooltip": { + "title": "Genera indice", + "description": "Crea automaticamente un indice cliccabile nel PDF unito basato sui nomi dei file originali e sui numeri di pagina." + }, "submit": "Unisci", "sortBy": { - "filename": "Nome file" + "description": "I file verranno uniti nell’ordine in cui sono selezionati. Trascina per riordinare o ordina qui sotto.", + "label": "Ordina per", + "filename": "Nome file", + "dateModified": "Data di modifica", + "ascending": "Crescente", + "descending": "Decrescente", + "sort": "Ordina" + }, + "error": { + "failed": "Si è verificato un errore durante l’unione dei PDF." } }, "split": { @@ -676,25 +673,207 @@ "splitPages": "Inserisci pagine a cui dividere:", "submit": "Dividi", "steps": { + "chooseMethod": "Scegli metodo", "settings": "Impostazioni" }, + "settings": { + "selectMethodFirst": "Seleziona prima un metodo di divisione" + }, + "error": { + "failed": "Si è verificato un errore durante la divisione del PDF." + }, + "method": { + "label": "Scegli metodo di divisione", + "placeholder": "Seleziona come dividere il PDF" + }, "methods": { + "prefix": { + "splitAt": "Dividi a", + "splitBy": "Dividi per" + }, + "byPages": { + "name": "Numeri di pagina", + "desc": "Estrai pagine specifiche (1,3,5-10)", + "tooltip": "Inserisci numeri di pagina separati da virgole o intervalli con trattini" + }, + "bySections": { + "name": "Sezioni", + "desc": "Dividi le pagine in sezioni a griglia", + "tooltip": "Dividi ogni pagina in sezioni orizzontali e verticali" + }, "bySize": { - "name": "Dimensione" + "name": "Dimensione", + "desc": "Limita la dimensione massima del file", + "tooltip": "Specifica dimensione massima (es. 10MB, 500KB)" + }, + "byPageCount": { + "name": "Conteggio pagine", + "desc": "Pagine fisse per file", + "tooltip": "Inserisci il numero di pagine per ogni file" + }, + "byDocCount": { + "name": "Numero documenti", + "desc": "Crea un numero specifico di file", + "tooltip": "Inserisci quanti file vuoi creare" + }, + "byChapters": { + "name": "Capitoli", + "desc": "Dividi ai limiti dei segnalibri", + "tooltip": "Usa i segnalibri del PDF per determinare i punti di divisione" + }, + "byPageDivider": { + "name": "Foglio divisore", + "desc": "Auto‑divisione con fogli divisori", + "tooltip": "Usa fogli divisori con QR code tra documenti durante la scansione" } }, "value": { "fileSize": { - "label": "Dimensione" + "label": "Dimensione", + "placeholder": "es., 10MB, 500KB" + }, + "pageCount": { + "label": "Pagine per file", + "placeholder": "es., 5, 10" + }, + "docCount": { + "label": "Numero di file", + "placeholder": "es., 3, 5" + } + }, + "tooltip": { + "header": { + "title": "Panoramica metodi di divisione" + }, + "byPages": { + "title": "Dividi a numeri di pagina", + "text": "Dividi il PDF in corrispondenza di numeri di pagina specifici. Usare 'n' divide dopo la pagina n. Usare 'n-m' divide prima di n e dopo m.", + "bullet1": "Punti singoli: 3,7 (divide dopo le pagine 3 e 7)", + "bullet2": "Intervalli: 3-8 (divide prima della 3 e dopo la 8)", + "bullet3": "Misto: 2,5-10,15 (divide dopo 2, prima di 5, dopo 10 e dopo 15)" + }, + "bySections": { + "title": "Dividi per sezioni a griglia", + "text": "Dividi ogni pagina in una griglia di sezioni. Utile per documenti con più colonne o per estrarre aree specifiche.", + "bullet1": "Orizzontale: numero di righe da creare", + "bullet2": "Verticale: numero di colonne da creare", + "bullet3": "Unisci: combina tutte le sezioni in un PDF" + }, + "bySize": { + "title": "Dividi per dimensione file", + "text": "Crea più PDF che non superino una dimensione specificata. Ideale per limiti di dimensione o allegati email.", + "bullet1": "Usa MB per file più grandi (es., 10MB)", + "bullet2": "Usa KB per file piccoli (es., 500KB)", + "bullet3": "Il sistema divide ai confini pagina" + }, + "byCount": { + "title": "Dividi per conteggio", + "text": "Crea più PDF con un numero specifico di pagine o documenti ciascuno.", + "bullet1": "Conteggio pagine: numero fisso per file", + "bullet2": "Numero documenti: numero fisso di file in output", + "bullet3": "Utile per workflow batch" + }, + "byChapters": { + "title": "Dividi per capitoli", + "text": "Usa i segnalibri del PDF per dividere automaticamente ai confini dei capitoli. Richiede PDF con struttura segnalibri.", + "bullet1": "Livello segnalibro: livello su cui dividere (1=primo livello)", + "bullet2": "Includi metadati: preserva le proprietà del documento", + "bullet3": "Consenti duplicati: gestisce nomi segnalibro ripetuti" } } }, "rotate": { "tags": "lato server", "title": "Ruota PDF", - "header": "Ruota PDF", - "selectAngle": "Scegli angolo di rotazione (in multipli di 90 gradi):", - "submit": "Ruota" + "submit": "Ruota", + "error": { + "failed": "Si è verificato un errore durante la rotazione del PDF." + }, + "preview": { + "title": "Anteprima rotazione" + }, + "rotateLeft": "Ruota in senso antiorario", + "rotateRight": "Ruota in senso orario", + "tooltip": { + "header": { + "title": "Panoramica impostazioni Rotazione" + }, + "description": { + "text": "Ruota le pagine del tuo PDF in incrementi di 90 gradi in senso orario o antiorario. Tutte le pagine del PDF verranno ruotate. L’anteprima mostra come apparirà il documento dopo la rotazione." + }, + "controls": { + "title": "Controlli", + "text": "Usa i pulsanti di rotazione per regolare l’orientamento. Il pulsante sinistro ruota in senso antiorario, quello destro in senso orario. Ogni clic ruota di 90 gradi." + } + } + }, + "convert": { + "title": "Converti", + "desc": "Converti file tra diversi formati", + "files": "File", + "selectFilesPlaceholder": "Seleziona i file nella vista principale per iniziare", + "settings": "Impostazioni", + "conversionCompleted": "Conversione completata", + "results": "Risultati", + "defaultFilename": "file_convertito", + "conversionResults": "Risultati conversione", + "convertFrom": "Converti da", + "convertTo": "Converti in", + "sourceFormatPlaceholder": "Formato sorgente", + "targetFormatPlaceholder": "Formato di destinazione", + "selectSourceFormatFirst": "Seleziona prima un formato sorgente", + "outputOptions": "Opzioni di output", + "pdfOptions": "Opzioni PDF", + "imageOptions": "Opzioni immagine", + "colorType": "Tipo di colore", + "color": "Colore", + "greyscale": "Scala di grigi", + "blackwhite": "Bianco e nero", + "dpi": "DPI", + "output": "Output", + "single": "Singolo", + "multiple": "Multipli", + "fitOption": "Opzione di adattamento", + "maintainAspectRatio": "Mantieni rapporto d’aspetto", + "fitDocumentToPage": "Adatta documento alla pagina", + "fillPage": "Riempi la pagina", + "autoRotate": "Rotazione automatica", + "autoRotateDescription": "Ruota automaticamente le immagini per adattarle meglio alla pagina PDF", + "combineImages": "Combina immagini", + "combineImagesDescription": "Combina tutte le immagini in un unico PDF, oppure crea PDF separati per ogni immagine", + "webOptions": "Opzioni Web a PDF", + "zoomLevel": "Livello di zoom", + "emailOptions": "Opzioni Email a PDF", + "includeAttachments": "Includi allegati email", + "maxAttachmentSize": "Dimensione massima allegato (MB)", + "includeAllRecipients": "Includi destinatari CC e BCC nell’intestazione", + "downloadHtml": "Scarica file HTML intermedio invece del PDF", + "pdfaOptions": "Opzioni PDF/A", + "outputFormat": "Formato di output", + "pdfaNote": "PDF/A-1b è più compatibile, PDF/A-2b supporta più funzionalità.", + "pdfaDigitalSignatureWarning": "Il PDF contiene una firma digitale. Questo verrà rimosso nel passaggio successivo.", + "fileFormat": "Formato file", + "wordDoc": "Documento Word", + "wordDocExt": "Documento Word (.docx)", + "odtExt": "Testo OpenDocument (.odt)", + "pptExt": "PowerPoint (.pptx)", + "odpExt": "Presentazione OpenDocument (.odp)", + "txtExt": "Testo semplice (.txt)", + "rtfExt": "Rich Text Format (.rtf)", + "selectedFiles": "File selezionati", + "noFileSelected": "Nessun file selezionato. Usa il pannello file per aggiungere file.", + "convertFiles": "Converti file", + "converting": "Conversione in corso...", + "downloadConverted": "Scarica file convertito", + "errorNoFiles": "Seleziona almeno un file da convertire.", + "errorNoFormat": "Seleziona sia il formato sorgente sia quello di destinazione.", + "errorNotSupported": "La conversione da {{from}} a {{to}} non è supportata.", + "images": "Immagini", + "officeDocs": "Documenti Office (Word, Excel, PowerPoint)", + "imagesExt": "Immagini (JPG, PNG, ecc.)", + "markdown": "Markdown", + "textRtf": "Testo/RTF", + "grayscale": "Scala di grigi" }, "imageToPdf": { "tags": "conversione,img,jpg,immagine,foto" @@ -744,33 +923,190 @@ "upload": "Aggiungi immagine", "submit": "Aggiungi immagine" }, + "attachments": { + "tags": "incorporare,allegare,file,allegato,allegati", + "title": "Aggiungere allegati", + "header": "Aggiungi allegati", + "add": "Aggiungi allegato", + "remove": "Rimuovi allegato", + "embed": "Incorpora allegato", + "submit": "Aggiungi allegati" + }, "watermark": { - "tags": "Testo,ripetizione,etichetta,proprio,copyright,marchio,img,jpg,immagine,foto", "title": "Aggiungi Filigrana", - "header": "Aggiungi filigrana", - "customColor": "Colore testo personalizzato", - "selectText": { - "1": "Seleziona PDF a cui aggiungere la filigrana:", - "2": "Testo:", - "3": "Dimensione carattere:", - "4": "Rotazione (0-360):", - "5": "spazio orizzontale (tra ogni filigrana):", - "6": "spazio verticale (tra ogni filigrana):", - "7": "Opacità (0% - 100%):", - "8": "Tipo di filigrana:", - "9": "Immagine filigrana:", - "10": "Converti PDF in PDF-Immagine" - }, + "desc": "Aggiungi filigrane di testo o immagine ai PDF", + "completed": "Filigrana aggiunta", "submit": "Aggiungi Filigrana", - "type": { - "1": "Testo", - "2": "Immagine" + "filenamePrefix": "con_filigrana", + "error": { + "failed": "Si è verificato un errore durante l’aggiunta della filigrana al PDF." }, "watermarkType": { - "text": "Testo" + "text": "Testo", + "image": "Immagine" }, "settings": { - "fontSize": "Dimensione del font" + "type": "Tipo di filigrana", + "text": { + "label": "Testo filigrana", + "placeholder": "Inserisci testo filigrana" + }, + "image": { + "label": "Immagine filigrana", + "choose": "Scegli immagine", + "selected": "Selezionato: {{filename}}" + }, + "fontSize": "Dimensione del font", + "size": "Dimensione", + "alphabet": "Font/Lingua", + "color": "Colore filigrana", + "rotation": "Rotazione (gradi)", + "opacity": "Opacità (%)", + "spacing": { + "horizontal": "Spaziatura orizzontale", + "vertical": "Spaziatura verticale" + }, + "convertToImage": "Appiattisci pagine PDF in immagini" + }, + "alphabet": { + "roman": "Romano/Latino", + "arabic": "Arabo", + "japanese": "Giapponese", + "korean": "Coreano", + "chinese": "Cinese", + "thai": "Thai" + }, + "steps": { + "type": "Tipo di filigrana", + "wording": "Testo", + "textStyle": "Stile", + "formatting": "Formattazione", + "file": "File filigrana" + }, + "results": { + "title": "Risultati filigrana" + }, + "tooltip": { + "language": { + "title": "Supporto lingua", + "text": "Scegli l’impostazione lingua appropriata per garantire il corretto rendering del font del tuo testo." + }, + "appearance": { + "title": "Impostazioni aspetto", + "text": "Controlla l’aspetto e l’integrazione della filigrana nel documento.", + "bullet1": "Rotazione: da -360° a 360° per filigrane inclinate", + "bullet2": "Opacità: 0-100% per il controllo della trasparenza", + "bullet3": "Opacità minore crea filigrane più discrete" + }, + "spacing": { + "title": "Controllo spaziatura", + "text": "Regola la spaziatura tra le filigrane ripetute sulla pagina.", + "bullet1": "Spaziatura larghezza: distanza orizzontale tra filigrane", + "bullet2": "Spaziatura altezza: distanza verticale tra filigrane", + "bullet3": "Valori più alti creano pattern più diradati" + }, + "type": { + "header": { + "title": "Selezione tipo di filigrana" + }, + "description": { + "title": "Scegli il tipo di filigrana", + "text": "Seleziona tra filigrane di testo o immagine in base alle tue esigenze." + }, + "text": { + "title": "Filigrane testo", + "text": "Perfetto per aggiungere note di copyright, nomi aziendali o etichette di riservatezza. Supporta più lingue e colori personalizzati.", + "bullet1": "Font e lingue personalizzabili", + "bullet2": "Colori e trasparenza regolabili", + "bullet3": "Ideale per testo legale o di branding" + }, + "image": { + "title": "Filigrane immagine", + "text": "Usa loghi, timbri o qualsiasi immagine come filigrana. Ottimo per branding e identificazione visiva.", + "bullet1": "Carica qualsiasi formato immagine", + "bullet2": "Mantiene la qualità dell’immagine", + "bullet3": "Perfetto per loghi e timbri" + } + }, + "wording": { + "header": { + "title": "Contenuto testuale" + }, + "text": { + "title": "Testo filigrana", + "text": "Inserisci il testo che apparirà come filigrana nel documento.", + "bullet1": "Mantienilo conciso per una migliore leggibilità", + "bullet2": "Esempi comuni: 'CONFIDENZIALE', 'BOZZA', nome azienda", + "bullet3": "I caratteri emoji non sono supportati e verranno filtrati" + } + }, + "textStyle": { + "header": { + "title": "Stile del testo" + }, + "color": { + "title": "Selezione colore", + "text": "Scegli un colore che fornisca buon contrasto con il contenuto del documento.", + "bullet1": "Grigio chiaro (#d3d3d3) per filigrane discrete", + "bullet2": "Nero o colori scuri per alto contrasto", + "bullet3": "Colori personalizzati per branding" + }, + "language": { + "title": "Supporto lingua", + "text": "Scegli l’impostazione lingua appropriata per garantire il corretto rendering del font." + } + }, + "file": { + "header": { + "title": "Caricamento immagine" + }, + "upload": { + "title": "Selezione immagine", + "text": "Carica un file immagine da usare come filigrana.", + "bullet1": "Supporta formati comuni: PNG, JPG, GIF, BMP", + "bullet2": "PNG con trasparenza è la scelta migliore", + "bullet3": "Immagini ad alta risoluzione mantengono meglio la qualità" + }, + "recommendations": { + "title": "Buone pratiche", + "text": "Suggerimenti per risultati ottimali con filigrane immagine.", + "bullet1": "Usa loghi o timbri con sfondo trasparente", + "bullet2": "Design semplici funzionano meglio di immagini complesse", + "bullet3": "Considera la dimensione finale del documento quando scegli la risoluzione" + } + }, + "formatting": { + "header": { + "title": "Formattazione e layout" + }, + "size": { + "title": "Controllo dimensione", + "text": "Regola la dimensione della filigrana (testo o immagine).", + "bullet1": "Dimensioni maggiori creano filigrane più evidenti" + }, + "appearance": { + "title": "Impostazioni aspetto", + "text": "Controlla l’aspetto e l’integrazione della filigrana nel documento.", + "bullet1": "Rotazione: da -360° a 360° per filigrane inclinate", + "bullet2": "Opacità: 0-100% per il controllo della trasparenza", + "bullet3": "Opacità minore crea filigrane più discrete" + }, + "spacing": { + "title": "Controllo spaziatura", + "text": "Regola la spaziatura tra le filigrane ripetute sulla pagina.", + "bullet1": "Spaziatura orizzontale: distanza tra filigrane da sinistra a destra", + "bullet2": "Spaziatura verticale: distanza tra filigrane dall’alto al basso", + "bullet3": "Valori più alti creano pattern più diradati" + }, + "security": { + "title": "Opzione sicurezza", + "text": "Converti il PDF finale in formato basato su immagini per maggiore sicurezza.", + "bullet1": "Previene la selezione e la copia del testo", + "bullet2": "Rende le filigrane più difficili da rimuovere", + "bullet3": "Produce file di dimensioni maggiori", + "bullet4": "Ideale per contenuti sensibili o protetti da copyright" + } + } } }, "permissions": { @@ -795,50 +1131,148 @@ "removePages": { "tags": "Rimuovere pagine,eliminare pagine", "title": "Rimuovi", + "pageNumbers": { + "label": "Pagine da rimuovere", + "placeholder": "es., 1,3,5-8,10", + "error": "Formato numero di pagina non valido. Usa numeri, intervalli (1-5) o espressioni matematiche (2n+1)" + }, + "filenamePrefix": "pagine_rimosse", + "files": { + "placeholder": "Seleziona un file PDF nella vista principale per iniziare" + }, + "settings": { + "title": "Impostazioni" + }, + "tooltip": { + "header": { + "title": "Impostazioni Rimozione Pagine" + }, + "pageNumbers": { + "title": "Selezione pagine", + "text": "Specifica quali pagine rimuovere dal PDF. Puoi selezionare pagine singole, intervalli o usare espressioni matematiche.", + "bullet1": "Pagine singole: 1,3,5 (rimuove pagine 1, 3 e 5)", + "bullet2": "Intervalli: 1-5,10-15 (rimuove 1-5 e 10-15)", + "bullet3": "Matematico: 2n+1 (rimuove pagine dispari)", + "bullet4": "Intervalli aperti: 5- (rimuove da pagina 5 alla fine)" + }, + "examples": { + "title": "Esempi comuni", + "text": "Ecco alcuni pattern comuni di selezione pagine:", + "bullet1": "Rimuovi prima pagina: 1", + "bullet2": "Rimuovi ultime 3 pagine: -3", + "bullet3": "Rimuovi una pagina sì e una no: 2n", + "bullet4": "Rimuovi pagine sparse specifiche: 1,5,10,15" + }, + "safety": { + "title": "Suggerimenti di sicurezza", + "text": "Considerazioni importanti quando rimuovi pagine:", + "bullet1": "Anteprima sempre la selezione prima di elaborare", + "bullet2": "Conserva un backup del file originale", + "bullet3": "La numerazione parte da 1, non da 0", + "bullet4": "I numeri di pagina non validi verranno ignorati" + } + }, + "error": { + "failed": "Si è verificato un errore durante la rimozione delle pagine." + }, + "results": { + "title": "Risultati rimozione pagine" + }, "submit": "Rimuovi" }, - "addPassword": { - "tags": "sicuro,sicurezza", - "title": "Aggiungi Password", - "header": "Aggiungi password (crittografa)", - "selectText": { - "1": "Seleziona PDF da crittografare", - "2": "Password", - "3": "Lunghezza chiave", - "4": "Valori più grandi sono più sicuri, ma valori più piccoli offrono una compatibilità maggiore.", - "5": "Permessi", - "6": "Previeni assemblaggio del documento", - "7": "Previeni estrazione del contenuto", - "8": "Previeni estrazione per accessibilità", - "9": "Previeni compilazione dei moduli", - "10": "Previeni modifiche", - "11": "Previeni annotazioni", - "12": "Previeni stampa", - "13": "Previeni stampa in diversi formati", - "14": "Password del proprietario", - "15": "Limita le operazioni eseguibili con il documento una volta aperto (non supportato da tutti i lettori)", - "16": "Limita l'apertura del documento stesso" - }, - "submit": "Crittografa", + "pageSelection": { "tooltip": { - "permissions": { - "title": "Cambia Permessi" + "header": { + "title": "Guida Selezione Pagine" + }, + "basic": { + "title": "Uso di base", + "text": "Seleziona pagine specifiche dal tuo PDF usando una sintassi semplice.", + "bullet1": "Pagine singole: 1,3,5", + "bullet2": "Intervalli: 3-6 o 10-15", + "bullet3": "Tutte le pagine: all" + }, + "advanced": { + "title": "Funzionalità avanzate" + }, + "tips": { + "title": "Suggerimenti", + "text": "Tieni presenti queste linee guida:", + "bullet1": "La numerazione delle pagine parte da 1 (non 0)", + "bullet2": "Gli spazi vengono rimossi automaticamente", + "bullet3": "Le espressioni non valide vengono ignorate" + }, + "syntax": { + "title": "Basi della sintassi", + "text": "Usa numeri, intervalli, parole chiave e progressioni (n parte da 0). Sono supportate le parentesi.", + "bullets": { + "numbers": "Numeri/intervalli: 5, 10-20", + "keywords": "Parole chiave: odd, even", + "progressions": "Progressioni: 3n, 4n+1" + } + }, + "operators": { + "title": "Operatori", + "text": "AND ha precedenza più alta della virgola. NOT si applica all’interno dell’intervallo del documento.", + "and": "AND: & o \"and\" — richiede entrambe le condizioni (es., 1-50 & even)", + "comma": "Virgola: , o | — combina selezioni (es., 1-10, 20)", + "not": "NOT: ! o \"not\" — esclude pagine (es., 3n & not 30)" + }, + "examples": { + "title": "Esempi" } } }, - "removePassword": { - "tags": "Decriptare,proteggere,rimuovere la password,eliminare la password", - "title": "Rimuovi Password", - "header": "Rimuovi password (de-crittografa)", - "selectText": { - "1": "Seleziona PDF da decrittare", - "2": "Password" + "bulkSelection": { + "header": { + "title": "Guida Selezione Pagine" }, - "submit": "Rimuovi Password", - "desc": "Rimuovi la password dal tuo PDF.", - "password": { - "stepTitle": "Rimuovi Password", - "label": "Password attuale" + "syntax": { + "title": "Basi della sintassi", + "text": "Usa numeri, intervalli, parole chiave e progressioni (n parte da 0). Sono supportate le parentesi.", + "bullets": { + "numbers": "Numeri/intervalli: 5, 10-20", + "keywords": "Parole chiave: odd, even", + "progressions": "Progressioni: 3n, 4n+1" + } + }, + "operators": { + "title": "Operatori", + "text": "AND ha precedenza più alta della virgola. NOT si applica all’interno dell’intervallo del documento.", + "and": "AND: & o \"and\" — richiede entrambe le condizioni (es., 1-50 & even)", + "comma": "Virgola: , o | — combina selezioni (es., 1-10, 20)", + "not": "NOT: ! o \"not\" — esclude pagine (es., 3n & not 30)" + }, + "examples": { + "title": "Esempi", + "first50": "Prime 50", + "last50": "Ultime 50", + "every3rd": "Ogni 3ª", + "oddWithinExcluding": "Dispari entro 1-20 escludendo 5-7", + "combineSets": "Combina insiemi" + }, + "firstNPages": { + "title": "Prime N pagine", + "placeholder": "Numero di pagine" + }, + "lastNPages": { + "title": "Ultime N pagine", + "placeholder": "Numero di pagine" + }, + "everyNthPage": { + "title": "Ogni N-esima pagina", + "placeholder": "Passo" + }, + "range": { + "title": "Intervallo", + "fromPlaceholder": "Da", + "toPlaceholder": "A" + }, + "keywords": { + "title": "Parole chiave" + }, + "advanced": { + "title": "Avanzate" } }, "compressPdfs": { @@ -848,28 +1282,142 @@ "tags": "rimuovi,elimina,modulo,campo,sola lettura", "title": "Rimuovi la sola lettura dai campi del modulo", "header": "Sbloccare i moduli PDF", - "submit": "Rimuovi" + "submit": "Rimuovi", + "description": "Questo strumento rimuoverà le restrizioni di sola lettura dai campi modulo PDF, rendendoli modificabili e compilabili.", + "filenamePrefix": "moduli_sbloccati", + "files": { + "placeholder": "Seleziona un file PDF nella vista principale per iniziare" + }, + "error": { + "failed": "Si è verificato un errore durante lo sblocco dei moduli PDF." + }, + "results": { + "title": "Risultati moduli sbloccati" + } }, "changeMetadata": { "tags": "Titolo,autore,data,creazione,ora,editore,produttore,statistiche", - "title": "Titolo:", "header": "Cambia Proprietà", - "selectText": { - "1": "Imposta i dati che vuoi cambiare", - "2": "Cancella tutte le proprietà", - "3": "Visualizza proprietà personalizzate:", - "4": "Altre proprietà:", - "5": "Aggiungi proprietà personalizzata:" + "submit": "Cambia proprietà", + "filenamePrefix": "metadati", + "settings": { + "title": "Impostazioni metadati" }, - "author": "Autore:", - "creationDate": "Data di creazione (yyyy/MM/dd HH:mm:ss):", - "creator": "Creatore:", - "keywords": "Parole chiave:", - "modDate": "Data di modifica (yyyy/MM/dd HH:mm:ss):", - "producer": "Produttore:", - "subject": "Oggetto:", - "trapped": "Recuperato:", - "submit": "Cambia proprietà" + "standardFields": { + "title": "Campi standard" + }, + "deleteAll": { + "label": "Rimuovi metadati esistenti", + "checkbox": "Elimina tutti i metadati" + }, + "title": { + "label": "Titolo", + "placeholder": "Titolo del documento" + }, + "author": { + "label": "Autore", + "placeholder": "Autore del documento" + }, + "subject": { + "label": "Oggetto", + "placeholder": "Oggetto del documento" + }, + "keywords": { + "label": "Parole chiave", + "placeholder": "Parole chiave del documento" + }, + "creator": { + "label": "Creatore", + "placeholder": "Creatore del documento" + }, + "producer": { + "label": "Produttore", + "placeholder": "Produttore del documento" + }, + "dates": { + "title": "Campi data" + }, + "creationDate": { + "label": "Data di creazione", + "placeholder": "Data di creazione" + }, + "modificationDate": { + "label": "Data di modifica", + "placeholder": "Data di modifica" + }, + "trapped": { + "label": "Stato Trapped", + "unknown": "Unknown", + "true": "True", + "false": "False" + }, + "advanced": { + "title": "Opzioni avanzate" + }, + "customFields": { + "title": "Metadati personalizzati", + "description": "Aggiungi campi metadati personalizzati al documento", + "add": "Aggiungi campo", + "key": "Chiave", + "keyPlaceholder": "Chiave personalizzata", + "value": "Valore", + "valuePlaceholder": "Valore personalizzato", + "remove": "Rimuovi" + }, + "results": { + "title": "PDF aggiornati" + }, + "error": { + "failed": "Si è verificato un errore durante la modifica dei metadati del PDF." + }, + "tooltip": { + "header": { + "title": "Panoramica metadati PDF" + }, + "standardFields": { + "title": "Campi standard", + "text": "Comuni campi metadati PDF che descrivono il documento.", + "bullet1": "Titolo: nome o intestazione del documento", + "bullet2": "Autore: persona che ha creato il documento", + "bullet3": "Oggetto: breve descrizione del contenuto", + "bullet4": "Parole chiave: termini di ricerca per il documento", + "bullet5": "Creatore/Produttore: software usato per creare il PDF" + }, + "dates": { + "title": "Campi data", + "text": "Quando il documento è stato creato e modificato.", + "bullet1": "Data di creazione: quando è stato creato il documento originale", + "bullet2": "Data di modifica: quando è stato modificato l’ultima volta" + }, + "options": { + "title": "Opzioni aggiuntive", + "text": "Campi personalizzati e controlli per la privacy.", + "bullet1": "Metadati personalizzati: aggiungi le tue coppie chiave‑valore", + "bullet2": "Stato Trapped: impostazione per stampa di alta qualità", + "bullet3": "Elimina tutto: rimuove tutti i metadati per la privacy" + }, + "deleteAll": { + "title": "Rimuovi metadati esistenti", + "text": "Eliminazione completa dei metadati per garantire la privacy." + }, + "customFields": { + "title": "Metadati personalizzati", + "text": "Aggiungi le tue coppie chiave‑valore personalizzate.", + "bullet1": "Aggiungi eventuali campi personalizzati rilevanti per il tuo documento", + "bullet2": "Esempi: Reparto, Progetto, Versione, Stato", + "bullet3": "Per ogni voce sono richiesti sia chiave che valore" + }, + "advanced": { + "title": "Opzioni avanzate", + "trapped": { + "title": "Stato Trapped", + "description": "Indica se il documento è preparato per la stampa di alta qualità.", + "bullet1": "True: Il documento è stato trappato per la stampa", + "bullet2": "False: Il documento non è stato trappato", + "bullet3": "Unknown: Stato di trapping non specificato" + } + } + } }, "fileToPDF": { "tags": "trasformazione,formato,documento,immagine,diapositiva,testo,conversione,ufficio,documenti,parola,excel,powerpoint", @@ -883,6 +1431,7 @@ "ocr": { "tags": "riconoscimento,testo,immagine,scansione,lettura,identificazione,rilevamento,modificabile", "title": "OCR / Pulisci scansioni", + "desc": "Pulisci scansioni ed estrai testo da immagini, convertendo le immagini in testo puro.", "header": "Pulisci scansioni / OCR (riconoscimento testo)", "selectText": { "1": "Scegli lingue da usare per il riconoscimento testo (L'elenco contiene quelle attualmente disponibili):", @@ -901,22 +1450,85 @@ "help": "Per favore leggi la documentazione su come usare il programma per altri linguaggi e/o uso non in Docker", "credit": "Questo servizio utilizza Qpdf e Tesseract per l'OCR.", "submit": "Scansiona testo nel PDF con OCR", - "desc": "Pulisci scansioni ed estrai testo da immagini, convertendo le immagini in testo puro.", + "operation": { + "submit": "Esegui OCR e rivedi" + }, + "results": { + "title": "Risultati OCR" + }, + "languagePicker": { + "additionalLanguages": "Cerchi lingue aggiuntive?", + "viewSetupGuide": "Vedi guida di configurazione →" + }, "settings": { "title": "Impostazioni", "ocrMode": { - "label": "Modalità OCR" + "label": "Modalità OCR", + "auto": "Automatico (salta i layer di testo)", + "force": "Forza (ri‑OCR di tutto, sostituisce testo)", + "strict": "Rigido (interrompe se trova testo)" }, "languages": { - "label": "Lingue" + "label": "Lingue", + "placeholder": "Seleziona lingue" + }, + "compatibilityMode": { + "label": "Modalità compatibilità" + }, + "advancedOptions": { + "label": "Opzioni di elaborazione", + "sidecar": "Crea file di testo", + "deskew": "Raddrizza pagine", + "clean": "Pulisci file di input", + "cleanFinal": "Pulisci output finale" } }, "tooltip": { + "header": { + "title": "Panoramica impostazioni OCR" + }, "mode": { - "title": "Modalità OCR" + "title": "Modalità OCR", + "text": "L’OCR (Riconoscimento Ottico dei Caratteri) ti aiuta a trasformare pagine scansionate o schermate in testo ricercabile, copiabile o evidenziabile.", + "bullet1": "Automatico salta le pagine che hanno già layer di testo.", + "bullet2": "Forza riesegue l’OCR su ogni pagina e sostituisce tutto il testo.", + "bullet3": "Rigido si interrompe se trova testo selezionabile." }, "languages": { - "title": "Lingue" + "title": "Lingue", + "text": "Migliora l’accuratezza OCR specificando le lingue attese. Scegline una o più per guidare il rilevamento." + }, + "output": { + "title": "Output", + "text": "Decidi come vuoi formattare l’output del testo:", + "bullet1": "PDF ricercabile incorpora il testo dietro l’immagine originale.", + "bullet2": "HOCR XML restituisce un file strutturato leggibile dalle macchine.", + "bullet3": "Sidecar testo semplice crea un file .txt con contenuto grezzo." + }, + "advanced": { + "header": { + "title": "Elaborazione OCR avanzata" + }, + "compatibility": { + "title": "Modalità compatibilità", + "text": "Usa la modalità OCR 'sandwich PDF': produce file più grandi ma più affidabili con alcune lingue e software PDF più vecchi. Per impostazione predefinita usiamo hOCR per PDF più piccoli e moderni." + }, + "sidecar": { + "title": "Crea file di testo", + "text": "Genera un file .txt separato insieme al PDF contenente tutto il testo estratto per un facile accesso ed elaborazione." + }, + "deskew": { + "title": "Raddrizza pagine", + "text": "Corregge automaticamente pagine storte o inclinate per migliorare l’accuratezza OCR. Utile per documenti scansionati non perfettamente allineati." + }, + "clean": { + "title": "Pulisci file di input", + "text": "Pre‑elabora l’input rimuovendo rumore, migliorando il contrasto e ottimizzando l’immagine per un OCR migliore prima dell’elaborazione." + }, + "cleanFinal": { + "title": "Pulisci output finale", + "text": "Post‑elabora il PDF finale rimuovendo artefatti OCR e ottimizzando il layer di testo per migliore leggibilità e dimensioni minori." + } } } }, @@ -1028,27 +1640,107 @@ "header": "Appiattisci PDF", "flattenOnlyForms": "Appiattisci solo i moduli", "submit": "Appiattisci", + "filenamePrefix": "piatto", + "files": { + "placeholder": "Seleziona un file PDF nella vista principale per iniziare" + }, "steps": { "settings": "Impostazioni" }, "options": { - "flattenOnlyForms": "Appiattisci solo i moduli" + "stepTitle": "Opzioni di flattening", + "title": "Opzioni di flattening", + "flattenOnlyForms.desc": "Appiattisci solo i campi modulo, lasciando intatti gli altri elementi interattivi", + "note": "Il flattening rimuove gli elementi interattivi dal PDF, rendendoli non modificabili." + }, + "results": { + "title": "Risultati Flatten" + }, + "error": { + "failed": "Si è verificato un errore durante il flattening del PDF." + }, + "tooltip": { + "header": { + "title": "Informazioni sul flattening dei PDF" + }, + "description": { + "title": "Cosa fa il flattening?", + "text": "Il flattening rende il tuo PDF non modificabile trasformando moduli compilabili e pulsanti in testo e immagini normali. Il PDF avrà lo stesso aspetto, ma nessuno potrà più modificare o compilare i moduli. Perfetto per condividere moduli completati, creare documenti finali per gli archivi o garantire un aspetto uniforme ovunque.", + "bullet1": "Le caselle di testo diventano testo normale (non modificabile)", + "bullet2": "Checkbox e pulsanti diventano immagini", + "bullet3": "Ottimo per versioni finali che non vuoi vengano modificate", + "bullet4": "Garantisce aspetto coerente su tutti i dispositivi" + }, + "formsOnly": { + "title": "Cosa significa 'Appiattisci solo i moduli'?", + "text": "Questa opzione rimuove solo la possibilità di compilare i moduli ma mantiene altre funzionalità come cliccare i link, vedere i segnalibri e leggere i commenti.", + "bullet1": "I moduli diventano non modificabili", + "bullet2": "I collegamenti restano cliccabili", + "bullet3": "Commenti e note restano visibili", + "bullet4": "I segnalibri aiutano ancora la navigazione" + } } }, "repair": { "tags": "aggiustare,ripristinare,correggere,recuperare", "title": "Ripara", "header": "Ripara PDF", - "submit": "Ripara" + "submit": "Ripara", + "description": "Questo strumento tenterà di riparare file PDF corrotti o danneggiati. Non sono richieste impostazioni aggiuntive.", + "filenamePrefix": "riparato", + "files": { + "placeholder": "Seleziona un file PDF nella vista principale per iniziare" + }, + "error": { + "failed": "Si è verificato un errore durante la riparazione del PDF." + }, + "results": { + "title": "Risultati riparazione" + } }, "removeBlanks": { "tags": "pulire,semplificare,non contenere contenuti,organizzare", "title": "Rimuovi spazi vuoti", "header": "Rimuovi pagine vuote", - "threshold": "Soglia:", - "thresholdDesc": "Soglia che determina un pixel 'bianco'", - "whitePercent": "Percentuale di bianco (%):", - "whitePercentDesc": "Percentuale della pagina che deve essere bianca per venire rimossa", + "settings": { + "title": "Impostazioni" + }, + "threshold": { + "label": "Soglia di bianchezza pixel" + }, + "whitePercent": { + "label": "Soglia percentuale di bianco", + "unit": "%" + }, + "includeBlankPages": { + "label": "Includi pagine bianche rilevate" + }, + "tooltip": { + "header": { + "title": "Impostazioni Rimozione Pagine Bianche" + }, + "threshold": { + "title": "Soglia di bianchezza pixel", + "text": "Controlla quanto un pixel deve essere 'bianco' per essere considerato tale. Aiuta a determinare cosa conta come area bianca sulla pagina.", + "bullet1": "0 = Nero puro (più restrittivo)", + "bullet2": "128 = Grigio medio", + "bullet3": "255 = Bianco puro (meno restrittivo)" + }, + "whitePercent": { + "title": "Soglia percentuale di bianco", + "text": "Imposta la percentuale minima di pixel bianchi richiesta perché una pagina sia considerata bianca e venga rimossa.", + "bullet1": "Valori più bassi (es., 80%) = più pagine rimosse", + "bullet2": "Valori più alti (es., 95%) = rimosse solo pagine molto bianche", + "bullet3": "Usa valori più alti per documenti con sfondi chiari" + }, + "includeBlankPages": { + "title": "Includi pagine bianche rilevate", + "text": "Se abilitato, crea un PDF separato contenente tutte le pagine bianche rilevate e rimosse dal documento originale.", + "bullet1": "Utile per rivedere ciò che è stato rimosso", + "bullet2": "Aiuta a verificare l’accuratezza del rilevamento", + "bullet3": "Può essere disabilitato per ridurre la dimensione del file di output" + } + }, "submit": "Rimuovi" }, "removeAnnotations": { @@ -1087,28 +1779,140 @@ "certSign": { "tags": "autenticare,PEM,P12,ufficiale,crittografare", "title": "Firma del certificato", - "header": "Firma un PDF con il tuo certificato (Lavoro in corso)", - "selectPDF": "Seleziona un file PDF per la firma:", - "jksNote": "Nota: se il tipo di certificato non è elencato di seguito, convertilo in un file Java Keystore (.jks) utilizzando lo strumento da riga di comando keytool. Quindi, scegli l'opzione del file .jks di seguito.", - "selectKey": "Seleziona il file della tua chiave privata (formato PKCS#8, potrebbe essere .pem o .der):", - "selectCert": "Seleziona il tuo file di certificato (formato X.509, potrebbe essere .pem o .der):", - "selectP12": "Selezionare il file keystore PKCS#12 (.p12 o .pfx) (facoltativo, se fornito, dovrebbe contenere la chiave privata e il certificato):", - "selectJKS": "Seleziona il tuo file Java Keystore (.jks o .keystore):", - "certType": "Tipo di certificato", - "password": "Inserisci la tua password dell'archivio chiavi o della chiave privata (se presente):", - "showSig": "Mostra firma", - "reason": "Motivo", - "location": "Posizione", - "name": "Nome", - "showLogo": "Mostra Logo", - "submit": "Firma PDF" + "filenamePrefix": "firmato", + "signMode": { + "stepTitle": "Modalità firma", + "tooltip": { + "header": { + "title": "Informazioni sulle firme PDF" + }, + "overview": { + "title": "Come funzionano le firme", + "text": "Entrambe le modalità sigillano il documento (ogni modifica è segnalata come manomissione) e registrano chi/quando/come per auditing. L’attendibilità del visualizzatore dipende dalla catena di certificazione." + }, + "manual": { + "title": "Manuale - Porta il tuo certificato", + "text": "Usa i tuoi file certificato per un’identità in linea col brand. Può mostrare Attendibile quando la tua CA/catena è riconosciuta.", + "use": "Usa per: verso clienti, legale, compliance." + }, + "auto": { + "title": "Automatico - Sigillo di sistema immediato, zero setup", + "text": "Firma con un certificato auto‑firmato del server. Stessa sigillatura anti‑manomissione e audit trail; in genere mostra Non verificato nei visualizzatori.", + "use": "Usa quando: serve velocità e identità interna coerente tra revisioni e archivi." + }, + "rule": { + "title": "Regola pratica", + "text": "Serve stato Attendibile per il destinatario? Manuale. Serve un sigillo anti‑manomissione rapido e audit trail senza setup? Automatico." + } + } + }, + "certTypeStep": { + "stepTitle": "Formato certificato" + }, + "certFiles": { + "stepTitle": "File certificato" + }, + "appearance": { + "stepTitle": "Aspetto firma", + "tooltip": { + "header": { + "title": "Informazioni sull’aspetto della firma" + }, + "invisible": { + "title": "Firme invisibili", + "text": "La firma viene aggiunta al PDF per la sicurezza ma non sarà visibile durante la visualizzazione. Perfetta per esigenze legali senza modificare l’aspetto del documento.", + "bullet1": "Fornisce sicurezza senza cambiamenti visivi", + "bullet2": "Soddisfa i requisiti legali per la firma digitale", + "bullet3": "Non influisce sul layout o sul design del documento" + }, + "visible": { + "title": "Firme visibili", + "text": "Mostra un riquadro firma sul PDF con nome, data e dettagli opzionali. Utile quando vuoi che i lettori vedano chiaramente che il documento è firmato.", + "bullet1": "Mostra nome firmatario e data sul documento", + "bullet2": "Può includere motivo e luogo della firma", + "bullet3": "Scegli in quale pagina posizionare la firma", + "bullet4": "Logo opzionale includibile" + } + } + }, + "sign": { + "submit": "Firma PDF", + "results": "PDF firmato" + }, + "error": { + "failed": "Si è verificato un errore durante l’elaborazione delle firme." + }, + "tooltip": { + "header": { + "title": "Gestione firme: informazioni" + }, + "overview": { + "title": "Cosa può fare questo strumento?", + "text": "Questo strumento ti consente di controllare se i tuoi PDF sono firmati digitalmente e di aggiungere nuove firme digitali. Le firme digitali dimostrano chi ha creato o approvato un documento e se è stato modificato dopo la firma.", + "bullet1": "Verifica le firme esistenti e la loro validità", + "bullet2": "Vedi informazioni dettagliate su firmatari e certificati", + "bullet3": "Aggiungi nuove firme digitali per proteggere i documenti", + "bullet4": "Supporto per più file con navigazione semplice" + }, + "validation": { + "title": "Verifica delle firme", + "text": "Quando controlli le firme, lo strumento indica se sono valide, chi ha firmato, quando e se il documento è stato modificato dopo la firma.", + "bullet1": "Mostra se le firme sono valide o non valide", + "bullet2": "Visualizza informazioni sul firmatario e la data di firma", + "bullet3": "Controlla se il documento è stato modificato dopo la firma", + "bullet4": "Può usare certificati personalizzati per la verifica" + }, + "signing": { + "title": "Aggiunta delle firme", + "text": "Per firmare un PDF è necessario un certificato digitale (come PEM, PKCS12 o JKS). Puoi scegliere di rendere la firma visibile sul documento o mantenerla invisibile solo per sicurezza.", + "bullet1": "Supporta formati PEM, PKCS12, JKS e certificato server", + "bullet2": "Opzione per mostrare o nascondere la firma nel PDF", + "bullet3": "Aggiungi motivo, luogo e nome del firmatario", + "bullet4": "Scegli in quale pagina posizionare le firme visibili", + "bullet5": "Usa certificato server per semplice opzione 'Firma con Stirling-PDF'" + } + }, + "certType": { + "tooltip": { + "header": { + "title": "Informazioni sui tipi di certificato" + }, + "what": { + "title": "Che cos’è un certificato?", + "text": "È un’identità sicura per la tua firma che prova che hai firmato. A meno che non ti sia richiesto di firmare con certificato, consigliamo un altro metodo sicuro come Digitata, Disegna o Carica." + }, + "which": { + "title": "Quale opzione dovrei usare?", + "text": "Scegli il formato che corrisponde al tuo file di certificato:", + "bullet1": "PKCS#12 (.p12 / .pfx) – file unico combinato (più comune)", + "bullet2": "PFX (.pfx) – versione Microsoft di PKCS12", + "bullet3": "PEM – file .pem separati di chiave privata e certificato", + "bullet4": "JKS – keystore Java .jks per workflow dev / CI-CD" + }, + "convert": { + "title": "Chiave non elencata?", + "text": "Converti il tuo file in un keystore Java (.jks) con keytool, poi scegli JKS." + } + } + } }, "removeCertSign": { "tags": "autenticare,PEM,P12,ufficiale,decifrare", "title": "Rimuovi certificato della firma", "header": "Rimuovere il certificato digitale dal PDF", "selectPDF": "Seleziona un file PDF:", - "submit": "Rimuovi firma" + "submit": "Rimuovi firma", + "description": "Questo strumento rimuoverà le firme digitali dei certificati dal tuo PDF.", + "filenamePrefix": "non_firmato", + "files": { + "placeholder": "Seleziona un file PDF nella vista principale per iniziare" + }, + "error": { + "failed": "Si è verificato un errore durante la rimozione delle firme certificato." + }, + "results": { + "title": "Risultati rimozione certificati" + } }, "pageLayout": { "tags": "unire,comporre,visualizzazione singola,organizzare", @@ -1118,8 +1922,100 @@ "addBorder": "Aggiungi bordi", "submit": "Invia" }, + "bookletImposition": { + "tags": "libretto,imposizione,stampa,rilegatura,piegatura,signatura", + "title": "Imposizione a libretto", + "header": "Imposizione a libretto", + "submit": "Crea libretto", + "spineLocation": { + "label": "Posizione dorso", + "left": "Sinistra (Standard)", + "right": "Destra (RTL)" + }, + "doubleSided": { + "label": "Stampa fronte‑retro", + "tooltip": "Crea sia fronte che retro per una corretta stampa del libretto" + }, + "manualDuplex": { + "title": "Modalità duplex manuale", + "instructions": "Per stampanti senza duplex automatico. Dovrai eseguire due volte:" + }, + "duplexPass": { + "label": "Passaggio di stampa", + "first": "1° passaggio", + "second": "2° passaggio", + "firstInstructions": "Stampa i fronti → impila a faccia in giù → esegui di nuovo con 2° passaggio", + "secondInstructions": "Carica la pila stampata a faccia in giù → stampa i retro" + }, + "rtlBinding": { + "label": "Rilegatura da destra a sinistra", + "tooltip": "Per arabo, ebraico o altre lingue RTL" + }, + "addBorder": { + "label": "Aggiungi bordi attorno alle pagine", + "tooltip": "Aggiunge bordi intorno a ogni sezione di pagina per aiutare con il taglio e l’allineamento" + }, + "addGutter": { + "label": "Aggiungi margine interno (gutter)", + "tooltip": "Aggiunge spazio interno per la rilegatura" + }, + "gutterSize": { + "label": "Dimensione gutter (punti)" + }, + "flipOnShortEdge": { + "label": "Capovolgi sul lato corto (solo duplex automatico)", + "tooltip": "Abilita per la stampa duplex sul lato corto (solo duplex automatico - ignorato in modalità manuale)", + "manualNote": "Non necessario in modalità manuale: capovolgi tu la pila" + }, + "advanced": { + "toggle": "Opzioni avanzate" + }, + "paperSizeNote": "La dimensione della carta è ricavata automaticamente dalla prima pagina.", + "tooltip": { + "header": { + "title": "Guida alla creazione del libretto" + }, + "description": { + "title": "Cos’è l’imposizione a libretto?", + "text": "Crea libretti professionali disponendo le pagine nel corretto ordine di stampa. Le pagine del PDF sono posizionate 2-up su fogli orizzontali così, una volta piegati e rilegati, leggono in sequenza come un vero libro." + }, + "example": { + "title": "Esempio: libretto da 8 pagine", + "text": "Il tuo documento di 8 pagine diventa 2 fogli:", + "bullet1": "Foglio 1 Fronte: Pagine 8, 1 | Retro: Pagine 2, 7", + "bullet2": "Foglio 2 Fronte: Pagine 6, 3 | Retro: Pagine 4, 5", + "bullet3": "Quando piegato e impilato: Legge 1→2→3→4→5→6→7→8" + }, + "printing": { + "title": "Come stampare e assemblare", + "text": "Segui questi passaggi per libretti perfetti:", + "bullet1": "Stampa fronte‑retro con 'Capovolgi sul lato lungo'", + "bullet2": "Impila i fogli in ordine, piegali a metà", + "bullet3": "Pinza o rilega lungo il dorso piegato", + "bullet4": "Per stampanti lato corto: abilita 'Capovolgi sul lato corto'" + }, + "manualDuplex": { + "title": "Duplex manuale (stampanti solo fronte)", + "text": "Per stampanti senza duplex automatico:", + "bullet1": "Disattiva 'Stampa fronte‑retro'", + "bullet2": "Seleziona '1° passaggio' → Stampa → impila a faccia in giù", + "bullet3": "Seleziona '2° passaggio' → carica la pila → stampa i retro", + "bullet4": "Piega e assembla normalmente" + }, + "advanced": { + "title": "Opzioni avanzate", + "text": "Affina il tuo libretto:", + "bullet1": "Rilegatura RTL: per lingue da destra a sinistra", + "bullet2": "Bordi: mostra linee di taglio per rifilare", + "bullet3": "Margine gutter: aggiunge spazio per rilegare/pin", + "bullet4": "Capovolgimento lato corto: solo per stampanti duplex automatiche" + } + }, + "error": { + "failed": "Si è verificato un errore durante la creazione dell’imposizione a libretto." + } + }, "scalePages": { - "tags": "ridimensionare,modificare,dimensionare,adattare", "title": "Regola la scala della pagina", "header": "Regola la scala della pagina", "pageSize": "Dimensione di una pagina del documento.", @@ -1127,6 +2023,44 @@ "scaleFactor": "Livello di zoom (ritaglio) di una pagina.", "submit": "Invia" }, + "adjustPageScale": { + "tags": "ridimensiona,modifica,dimensione,adatta", + "title": "Regola Scala Pagina", + "header": "Regola Scala Pagina", + "scaleFactor": { + "label": "Fattore di scala" + }, + "pageSize": { + "label": "Dimensione pagina di destinazione", + "keep": "Mantieni dimensioni originali", + "letter": "Letter", + "legal": "Legal" + }, + "submit": "Regola scala pagina", + "error": { + "failed": "Si è verificato un errore durante la regolazione della scala della pagina." + }, + "tooltip": { + "header": { + "title": "Panoramica Impostazioni Scala Pagina" + }, + "description": { + "title": "Descrizione", + "text": "Regola la dimensione del contenuto PDF e cambia le dimensioni della pagina." + }, + "scaleFactor": { + "title": "Fattore di scala", + "text": "Controlla quanto grande o piccolo appare il contenuto sulla pagina. Il contenuto è scalato e centrato: se la dimensione scalata supera la dimensione pagina, potrebbe venire ritagliato.", + "bullet1": "1,0 = Dimensione originale", + "bullet2": "0,5 = Metà dimensione (50% più piccolo)", + "bullet3": "2,0 = Doppia dimensione (200% più grande, potrebbe ritagliare)" + }, + "pageSize": { + "title": "Dimensione pagina di destinazione", + "text": "Imposta le dimensioni delle pagine PDF in uscita. 'Mantieni dimensioni originali' conserva le dimensioni attuali, mentre le altre opzioni ridimensionano ai formati carta standard." + } + } + }, "add-page-numbers": { "tags": "impaginare,etichettare,organizzare,indicizzare" }, @@ -1134,7 +2068,29 @@ "tags": "rilevamento automatico,basato su intestazione,organizzazione,rietichettatura", "title": "Rinomina automatica", "header": "Rinomina automatica PDF", - "submit": "Rinomina automatica" + "description": "Trova automaticamente il titolo dal contenuto del PDF e lo usa come nome file.", + "submit": "Rinomina automatica", + "files": { + "placeholder": "Seleziona un file PDF nella vista principale per iniziare" + }, + "error": { + "failed": "Si è verificato un errore durante la rinomina automatica del PDF." + }, + "results": { + "title": "Risultati Rinomina Automatica" + }, + "tooltip": { + "header": { + "title": "Come funziona Rinomina Automatica" + }, + "howItWorks": { + "title": "Rinomina intelligente", + "text": "Trova automaticamente il titolo dal contenuto del PDF e lo usa come nome file.", + "bullet1": "Cerca testo che sembri un titolo o un’intestazione", + "bullet2": "Crea un nome file pulito e valido dal titolo rilevato", + "bullet3": "Mantiene il nome originale se non trova un titolo adatto" + } + } }, "adjust-contrast": { "tags": "correzione del colore,messa a punto,modifica,miglioramento" @@ -1143,7 +2099,36 @@ "tags": "tagliare,ridurre,modificare,modellare", "title": "Ritaglia", "header": "Ritaglia PDF", - "submit": "Invia" + "submit": "Invia", + "noFileSelected": "Seleziona un file PDF per iniziare il ritaglio", + "preview": { + "title": "Selezione area di ritaglio" + }, + "reset": "Reimposta all’intero PDF", + "coordinates": { + "title": "Posizione e dimensioni", + "x": "Posizione X", + "y": "Posizione Y", + "width": "Larghezza", + "height": "Altezza" + }, + "error": { + "invalidArea": "L’area di ritaglio supera i limiti del PDF", + "failed": "Impossibile ritagliare il PDF" + }, + "steps": { + "selectArea": "Seleziona area di ritaglio" + }, + "tooltip": { + "title": "Come ritagliare i PDF", + "description": "Seleziona l’area da ritagliare dal tuo PDF trascinando e ridimensionando la sovrapposizione blu sulla miniatura.", + "drag": "Trascina la sovrapposizione per spostare l’area di ritaglio", + "resize": "Trascina i bordi e gli angoli per ridimensionare", + "precision": "Usa gli input delle coordinate per un posizionamento preciso" + }, + "results": { + "title": "Risultati ritaglio" + } }, "autoSplitPDF": { "tags": "Basato su QR,separato,scansiona segmenti,organizza", @@ -1226,64 +2211,122 @@ "downloadJS": "Scarica Javascript", "submit": "Mostra" }, - "autoRedact": { - "tags": "Redigere,nascondere,oscurare,nero,pennarello,nascosto", - "title": "Redazione automatica", - "header": "Redazione automatica", - "colorLabel": "Colore", - "textsToRedactLabel": "Testo da oscurare (separato da righe)", - "textsToRedactPlaceholder": "per esempio. \\nConfidenziale \\nTop-Secret", - "useRegexLabel": "Usa Regex", - "wholeWordSearchLabel": "Ricerca di parole intere", - "customPaddingLabel": "Padding extra personalizzato", - "convertPDFToImageLabel": "Converti PDF in immagine PDF (utilizzato per rimuovere il testo dietro la casella)", - "submitButton": "Invia" - }, "redact": { "tags": "Redigere,nascondere,oscurare,nero,pennarello,nascosto,manuale", "title": "Redazione manuale", - "header": "Redazione manuale", "submit": "Redazione", - "textBasedRedaction": "Redazione basata sul testo", - "pageBasedRedaction": "Redazione basata sulla pagina", - "convertPDFToImageLabel": "Converti PDF in immagine PDF (utilizzato per rimuovere il testo dietro la casella)", - "pageRedactionNumbers": { - "title": "Pagine", - "placeholder": "(es. 1,2,8 o 4,7,12-16 o 2n-1)" + "error": { + "failed": "Si è verificato un errore durante la redazione del PDF." }, - "redactionColor": { - "title": "Colore di redazione" + "modeSelector": { + "title": "Metodo di redazione", + "mode": "Modalità", + "automatic": "Automatica", + "automaticDesc": "Redigi testo in base ai termini di ricerca", + "manual": "Manuale", + "manualDesc": "Clicca e trascina per redigere aree specifiche", + "manualComingSoon": "Redazione manuale in arrivo" }, - "export": "Esporta", - "upload": "Caricamento", - "boxRedaction": "Redazione del disegno della casella", - "zoom": "Zoom", - "zoomIn": "Ingrandisci", - "zoomOut": "Rimpicciolisci", - "nextPage": "Pagina successiva", - "previousPage": "Pagina precedente", - "toggleSidebar": "Attiva barra laterale", - "showThumbnails": "Mostra miniature", - "showDocumentOutline": "Mostra struttura documento (fare doppio clic per espandere/comprimere tutti gli elementi)", - "showAttatchments": "Mostra allegati", - "showLayers": "Mostra livelli (fare doppio clic per ripristinare tutti i livelli allo stato predefinito)", - "colourPicker": "Selettore colore", - "findCurrentOutlineItem": "Trova l'elemento di contorno corrente", - "applyChanges": "Applica modifiche", "auto": { + "header": "Redazione automatica", "settings": { + "title": "Impostazioni di redazione", "advancedTitle": "Avanzate" }, + "colorLabel": "Colore riquadro", "wordsToRedact": { - "add": "Aggiungi" + "title": "Parole da redigere", + "placeholder": "Inserisci una parola", + "add": "Aggiungi", + "examples": "Esempi: Riservato, Top‑Secret" + }, + "useRegexLabel": "Usa regex", + "wholeWordSearchLabel": "Cerca parola intera", + "customPaddingLabel": "Padding extra personalizzato", + "convertPDFToImageLabel": "Converti PDF in PDF‑immagine" + }, + "tooltip": { + "mode": { + "header": { + "title": "Metodo di redazione" + }, + "automatic": { + "title": "Redazione automatica", + "text": "Trova e redige automaticamente il testo specificato in tutto il documento. Perfetto per rimuovere informazioni sensibili ricorrenti come nomi, indirizzi o etichette riservate." + }, + "manual": { + "title": "Redazione manuale", + "text": "Clicca e trascina per selezionare manualmente le aree da redigere. Offre controllo preciso su cosa viene redatto. (In arrivo)" + } + }, + "words": { + "header": { + "title": "Parole da redigere" + }, + "description": { + "title": "Corrispondenza testo", + "text": "Inserisci parole o frasi da trovare e redigere nel documento. Ogni parola verrà cercata separatamente." + }, + "bullet1": "Aggiungi una parola alla volta", + "bullet2": "Premi Invio o clicca 'Aggiungi un’altra' per aggiungere", + "bullet3": "Clicca × per rimuovere parole", + "examples": { + "title": "Esempi comuni", + "text": "Parole tipiche da redigere includono: coordinate bancarie, indirizzi email o nomi specifici." + } + }, + "advanced": { + "header": { + "title": "Impostazioni di redazione avanzate" + }, + "color": { + "title": "Colore riquadro e padding", + "text": "Personalizza l’aspetto dei riquadri di redazione. Il nero è standard, ma puoi scegliere qualsiasi colore. Il padding aggiunge spazio extra attorno al testo trovato." + }, + "regex": { + "title": "Usa regex", + "text": "Abilita espressioni regolari per ricerche avanzate. Utile per trovare numeri di telefono, email o pattern complessi.", + "bullet1": "Esempio: \\d{4}-\\d{2}-\\d{2} per trovare date in formato AAAA-MM-GG", + "bullet2": "Usare con cautela - testa accuratamente" + }, + "wholeWord": { + "title": "Ricerca parola intera", + "text": "Abbina solo parole complete, non corrispondenze parziali. 'Mario' non corrisponderà a 'Marioni' quando abilitato." + }, + "convert": { + "title": "Converti in PDF‑immagine", + "text": "Converte il PDF in un PDF basato su immagini dopo la redazione. Garantisce che il testo dietro i riquadri sia completamente rimosso e non recuperabile." + } } }, "manual": { + "header": "Redazione manuale", + "textBasedRedaction": "Redazione basata su testo", + "pageBasedRedaction": "Redazione per pagina", + "convertPDFToImageLabel": "Converti PDF in PDF‑immagine (usato per rimuovere testo dietro il riquadro)", "pageRedactionNumbers": { "title": "Pagine", "placeholder": "(es. 1,2,8 o 4,7,12-16 o 2n-1)" }, - "export": "Esporta" + "redactionColor": { + "title": "Colore redazione" + }, + "export": "Esporta", + "upload": "Carica", + "boxRedaction": "Redazione con riquadro", + "zoom": "Zoom", + "zoomIn": "Ingrandisci", + "zoomOut": "Riduci", + "nextPage": "Pagina successiva", + "previousPage": "Pagina precedente", + "toggleSidebar": "Mostra/Nascondi barra laterale", + "showThumbnails": "Mostra miniature", + "showDocumentOutline": "Mostra sommario documento (doppio clic per espandere/comprimere tutte le voci)", + "showAttachments": "Mostra allegati", + "showLayers": "Mostra livelli (doppio clic per reimpostare tutti i livelli allo stato predefinito)", + "colourPicker": "Selettore colore", + "findCurrentOutlineItem": "Trova voce sommario corrente", + "applyChanges": "Applica modifiche" } }, "tableExtraxt": { @@ -1337,6 +2380,7 @@ "tags": "Timbro,Aggiungi immagine,Centra immagine,Filigrana,PDF,Incorpora,Personalizza", "header": "Timbro PDF", "title": "Timbro PDF", + "stampSetup": "Impostazione Timbro", "stampType": "Tipo di timbro", "stampText": "Testo del timbro", "stampImage": "Immagine del timbro", @@ -1349,7 +2393,8 @@ "overrideY": "Sostituisci la coordinata Y", "customMargin": "Margine personalizzato", "customColor": "Colore testo personalizzato", - "submit": "Invia" + "submit": "Invia", + "noStampSelected": "Nessun timbro selezionato. Torna al Passo 1." }, "removeImagePdf": { "tags": "Rimuovi immagine,operazioni sulla pagina,back-end,lato server" @@ -1428,6 +2473,8 @@ "title": "Accedi", "header": "Accedi", "signin": "Accedi", + "signInWith": "Accedi con", + "signInAnonymously": "Registrati come ospite", "rememberme": "Ricordami", "invalid": "Nome utente o password errati.", "locked": "Il tuo account è stato bloccato.", @@ -1446,12 +2493,70 @@ "alreadyLoggedIn": "Hai già effettuato l'accesso a", "alreadyLoggedIn2": "dispositivi. Esci dai dispositivi e riprova.", "toManySessions": "Hai troppe sessioni attive", - "logoutMessage": "Sei stato disconnesso." + "logoutMessage": "Sei stato disconnesso.", + "youAreLoggedIn": "Hai effettuato l’accesso!", + "email": "Email", + "password": "Password", + "enterEmail": "Inserisci la tua email", + "enterPassword": "Inserisci la tua password", + "loggingIn": "Accesso in corso...", + "signingIn": "Accesso in corso...", + "login": "Accedi", + "or": "Oppure", + "useMagicLink": "Usa invece il magic link", + "enterEmailForMagicLink": "Inserisci la tua email per ricevere il magic link", + "sending": "Invio…", + "sendMagicLink": "Invia magic link", + "cancel": "Annulla", + "dontHaveAccount": "Non hai un account? Registrati", + "home": "Home", + "debug": "Debug", + "signOut": "Disconnettiti", + "pleaseEnterBoth": "Inserisci sia email che password", + "pleaseEnterEmail": "Inserisci il tuo indirizzo email", + "magicLinkSent": "Magic link inviato a {{email}}! Controlla la posta e clicca il link per accedere.", + "passwordResetSent": "Link di reimpostazione password inviato a {{email}}! Segui le istruzioni nell’email.", + "failedToSignIn": "Accesso con {{provider}} non riuscito: {{message}}", + "unexpectedError": "Errore imprevisto: {{message}}" + }, + "signup": { + "title": "Crea un account", + "subtitle": "Unisciti a Stirling PDF per iniziare", + "name": "Nome", + "email": "Email", + "password": "Password", + "confirmPassword": "Conferma password", + "enterName": "Inserisci il tuo nome", + "enterEmail": "Inserisci la tua email", + "enterPassword": "Inserisci la tua password", + "confirmPasswordPlaceholder": "Conferma password", + "or": "oppure", + "creatingAccount": "Creazione account...", + "signUp": "Registrati", + "alreadyHaveAccount": "Hai già un account? Accedi", + "pleaseFillAllFields": "Compila tutti i campi", + "passwordsDoNotMatch": "Le password non corrispondono", + "passwordTooShort": "La password deve contenere almeno 6 caratteri", + "invalidEmail": "Inserisci un indirizzo email valido", + "checkEmailConfirmation": "Controlla la tua email per il link di conferma per completare la registrazione.", + "accountCreatedSuccessfully": "Account creato con successo! Ora puoi accedere.", + "unexpectedError": "Errore imprevisto: {{message}}" }, "pdfToSinglePage": { "title": "PDF a pagina singola", "header": "PDF a pagina singola", - "submit": "Converti in pagina singola" + "submit": "Converti in pagina singola", + "description": "Questo strumento unirà tutte le pagine del tuo PDF in un’unica grande pagina. La larghezza resterà la stessa delle pagine originali, ma l’altezza sarà la somma delle altezze di tutte le pagine.", + "filenamePrefix": "pagina_unica", + "files": { + "placeholder": "Seleziona un file PDF nella vista principale per iniziare" + }, + "error": { + "failed": "Si è verificato un errore durante la conversione in pagina singola." + }, + "results": { + "title": "Risultati Pagina Unica" + } }, "pageExtracter": { "title": "Estrai pagine", @@ -1482,11 +2587,39 @@ }, "compress": { "title": "Comprimi", + "desc": "Comprimi i PDF per ridurne la dimensione.", "header": "Comprimi PDF", + "method": { + "title": "Metodo di compressione", + "quality": "Qualità", + "filesize": "Dimensione" + }, "credit": "Questo servizio utilizza qpdf per la compressione/ottimizzazione dei PDF.", "grayscale": { "label": "Applica scala di grigio per la compressione" }, + "tooltip": { + "header": { + "title": "Panoramica impostazioni di compressione" + }, + "description": { + "title": "Descrizione", + "text": "La compressione è un modo semplice per ridurre la dimensione del file. Scegli Dimensione File per inserire una dimensione obiettivo e lascia che regoliamo la qualità per te. Scegli Qualità per impostare manualmente l’intensità della compressione." + }, + "qualityAdjustment": { + "title": "Regolazione qualità", + "text": "Trascina il cursore per regolare l’intensità della compressione. Valori bassi (1‑3) preservano la qualità ma producono file più grandi. Valori alti (7‑9) riducono maggiormente la dimensione ma diminuiscono la nitidezza delle immagini.", + "bullet1": "Valori più bassi preservano la qualità", + "bullet2": "Valori più alti riducono la dimensione del file" + }, + "grayscale": { + "title": "Scala di grigi", + "text": "Seleziona questa opzione per convertire tutte le immagini in bianco e nero, il che può ridurre significativamente la dimensione, specialmente per PDF scansionati o ricchi di immagini." + } + }, + "error": { + "failed": "Si è verificato un errore durante la compressione del PDF." + }, "selectText": { "1": { "_value": "Impostazioni di compressione", @@ -1496,10 +2629,7 @@ "4": "Modalità automatica - Regola automaticamente la qualità per ottenere le dimensioni esatte del PDF", "5": "Dimensioni PDF previste (ad es. 25 MB, 10,8 MB, 25 KB)" }, - "submit": "Comprimi", - "method": { - "filesize": "Dimensione" - } + "submit": "Comprimi" }, "decrypt": { "passwordPrompt": "Questo file è protetto da password. Inserisci la password:", @@ -1634,6 +2764,12 @@ }, "note": "Le note di rilascio sono disponibili solo in inglese" }, + "swagger": { + "title": "Documentazione API", + "header": "Documentazione API", + "desc": "Visualizza e testa gli endpoint dell’API Stirling PDF", + "tags": "api,documentazione,swagger,endpoint,sviluppo" + }, "cookieBanner": { "popUp": { "title": "Come utilizziamo i cookie", @@ -1666,57 +2802,294 @@ "description": "Questi cookie sono essenziali per il corretto funzionamento del sito web. Abilitano funzionalità fondamentali come l'impostazione delle preferenze sulla privacy, l'accesso e la compilazione di moduli, motivo per cui non possono essere disattivati." }, "analytics": { - "title": "Analytics", + "title": "Analitiche", "description": "Questi cookie ci aiutano a capire come vengono utilizzati i nostri strumenti, così possiamo concentrarci sullo sviluppo delle funzionalità che la nostra community apprezza di più. Non preoccuparti: Stirling PDF non può e non traccerà mai il contenuto dei documenti con cui lavori." } } }, - "download": "Salva", - "undo": "Annulla", - "convert": { - "title": "Converti", - "settings": "Impostazioni", - "color": "Colore", - "greyscale": "Scala di grigi", - "fillPage": "Riempi la pagina", - "pdfaDigitalSignatureWarning": "Il PDF contiene una firma digitale. Questo verrà rimosso nel passaggio successivo.", - "grayscale": "Scala di grigi" + "removeMetadata": { + "submit": "Rimuovi metadati" }, - "attachments": { - "tags": "incorporare,allegare,file,allegato,allegati", - "title": "Aggiungere allegati", - "header": "Aggiungi allegati", - "submit": "Aggiungi allegati" + "sidebar": { + "toggle": "Mostra/Nascondi barra laterale" + }, + "theme": { + "toggle": "Cambia tema" + }, + "view": { + "viewer": "Visualizzatore", + "pageEditor": "Editor pagine", + "fileManager": "Gestore file" + }, + "pageEditor": { + "title": "Editor pagine", + "save": "Salva modifiche", + "noPdfLoaded": "Nessun PDF caricato. Carica un PDF per modificare.", + "rotatedLeft": "Ruotato a sinistra:", + "rotatedRight": "Ruotato a destra:", + "deleted": "Eliminato:", + "movedLeft": "Spostato a sinistra:", + "movedRight": "Spostato a destra:", + "splitAt": "Dividi a:", + "insertedPageBreak": "Inserito salto pagina a:", + "addFileNotImplemented": "Aggiunta file non implementata nella demo", + "closePdf": "Chiudi PDF", + "reset": "Reimposta modifiche", + "zoomIn": "Ingrandisci", + "zoomOut": "Riduci", + "fitToWidth": "Adatta alla larghezza", + "actualSize": "Dimensione reale" + }, + "viewer": { + "firstPage": "Prima pagina", + "lastPage": "Ultima pagina", + "previousPage": "Pagina precedente", + "nextPage": "Pagina successiva", + "zoomIn": "Ingrandisci", + "zoomOut": "Riduci", + "singlePageView": "Vista pagina singola", + "dualPageView": "Vista doppia pagina" }, "rightRail": { + "closeSelected": "Chiudi file selezionati", "selectAll": "Seleziona tutto", - "deselectAll": "Deseleziona tutto" + "deselectAll": "Deseleziona tutto", + "selectByNumber": "Seleziona per numeri di pagina", + "deleteSelected": "Elimina pagine selezionate", + "closePdf": "Chiudi PDF", + "exportAll": "Esporta PDF", + "downloadSelected": "Scarica file selezionati", + "downloadAll": "Scarica tutto", + "toggleTheme": "Cambia tema", + "language": "Lingua", + "search": "Cerca nel PDF", + "panMode": "Modalità mano", + "rotateLeft": "Ruota a sinistra", + "rotateRight": "Ruota a destra", + "toggleSidebar": "Mostra/Nascondi barra laterale" + }, + "search": { + "title": "Cerca nel PDF", + "placeholder": "Inserisci termine di ricerca..." + }, + "guestBanner": { + "title": "Stai usando Stirling PDF come ospite!", + "message": "Crea un account gratuito per salvare il tuo lavoro, accedere a più funzionalità e supportare il progetto.", + "dismiss": "Chiudi banner", + "signUp": "Registrati gratis" + }, + "toolPicker": { + "searchPlaceholder": "Cerca strumenti...", + "noToolsFound": "Nessuno strumento trovato", + "allTools": "TUTTI GLI STRUMENTI", + "quickAccess": "ACCESSO RAPIDO", + "categories": { + "standardTools": "Strumenti standard", + "advancedTools": "Strumenti avanzati", + "recommendedTools": "Strumenti consigliati" + }, + "subcategories": { + "signing": "Firma", + "documentSecurity": "Sicurezza documenti", + "verification": "Verifica", + "documentReview": "Revisione documenti", + "pageFormatting": "Formattazione pagine", + "extraction": "Estrazione", + "removal": "Rimozione", + "automation": "Automazione", + "general": "Generale", + "advancedFormatting": "Formattazione avanzata", + "developerTools": "Strumenti per sviluppatori" + } }, "quickAccess": { - "sign": "Firma" + "read": "Leggi", + "sign": "Firma", + "automate": "Automatizza", + "files": "File", + "activity": "Attività", + "config": "Config", + "allTools": "Tutti gli strumenti" }, "fileUpload": { + "selectFile": "Seleziona un file", + "selectFiles": "Seleziona file", + "selectPdfToView": "Seleziona un PDF da visualizzare", + "selectPdfToEdit": "Seleziona un PDF da modificare", + "chooseFromStorage": "Scegli un file dall’archiviazione o carica un nuovo PDF", + "chooseFromStorageMultiple": "Scegli file dall’archiviazione o carica nuovi PDF", + "loadFromStorage": "Carica dall’archiviazione", + "filesAvailable": "file disponibili", "loading": "Caricamento...", - "or": "o" + "or": "o", + "dropFileHere": "Rilascia qui il file o clicca per caricare", + "dropFilesHere": "Rilascia qui i file o clicca il pulsante di upload", + "pdfFilesOnly": "Solo file PDF", + "supportedFileTypes": "Tipi di file supportati", + "upload": "Carica", + "uploadFile": "Carica file", + "uploadFiles": "Carica file", + "noFilesInStorage": "Nessun file disponibile in archivio. Caricane alcuni prima.", + "selectFromStorage": "Seleziona dall’archiviazione", + "backToTools": "Torna agli strumenti", + "addFiles": "Aggiungi file", + "dragFilesInOrClick": "Trascina i file o clicca \"Aggiungi file\" per sfogliare" }, "fileManager": { + "title": "Carica file PDF", + "subtitle": "Aggiungi file al tuo archivio per un accesso facile tra gli strumenti", + "filesSelected": "file selezionati", + "clearSelection": "Deseleziona", + "openInFileEditor": "Apri nell’Editor File", + "uploadError": "Caricamento di alcuni file non riuscito.", + "failedToOpen": "Impossibile aprire il file. Potrebbe essere stato rimosso dall’archiviazione.", + "failedToLoad": "Caricamento del file nel set attivo non riuscito.", + "storageCleared": "Il browser ha svuotato l’archiviazione. I file sono stati rimossi. Ricaricali.", + "clearAll": "Pulisci tutto", + "reloadFiles": "Ricarica file", + "dragDrop": "Trascina e rilascia i file qui", + "clickToUpload": "Clicca per caricare file", + "selectedFiles": "File selezionati", + "storage": "Archiviazione", + "filesStored": "file archiviati", + "storageError": "Si è verificato un errore di archiviazione", + "storageLow": "Archiviazione quasi piena. Valuta la rimozione di file vecchi.", + "supportMessage": "Basato sul database del browser per capacità illimitata", + "noFileSelected": "Nessun file selezionato", + "showHistory": "Mostra cronologia", + "hideHistory": "Nascondi cronologia", + "fileHistory": "Cronologia file", + "loadingHistory": "Caricamento cronologia...", + "lastModified": "Ultima modifica", + "toolChain": "Strumenti applicati", + "restore": "Ripristina", + "searchFiles": "Cerca file...", + "recent": "Recenti", + "localFiles": "File locali", + "googleDrive": "Google Drive", + "googleDriveShort": "Drive", + "myFiles": "I miei file", + "noRecentFiles": "Nessun file recente trovato", + "dropFilesHint": "Rilascia qui per caricare", + "googleDriveNotAvailable": "Integrazione Google Drive non disponibile", + "openFiles": "Apri file", + "openFile": "Apri file", + "details": "Dettagli file", "fileName": "Nome", + "fileFormat": "Formato", + "fileSize": "Dimensione", "fileVersion": "Versione", + "totalSelected": "Totale selezionati", + "dropFilesHere": "Rilascia i file qui", "selectAll": "Seleziona tutto", "deselectAll": "Deseleziona tutto", "deleteSelected": "Elimina selezionata", + "downloadSelected": "Scarica selezionati", + "selectedCount": "{{count}} selezionati", "download": "Salva", - "delete": "Elimina" + "delete": "Elimina", + "unsupported": "Non supportato" + }, + "storage": { + "temporaryNotice": "I file sono archiviati temporaneamente nel tuo browser e potrebbero essere eliminati automaticamente", + "storageLimit": "Limite archiviazione", + "storageUsed": "Archiviazione temporanea utilizzata", + "storageFull": "Archiviazione quasi piena. Valuta di rimuovere alcuni file.", + "fileTooLarge": "File troppo grande. Dimensione massima per file", + "storageQuotaExceeded": "Quota di archiviazione superata. Rimuovi alcuni file prima di caricarne altri.", + "approximateSize": "Dimensione approssimativa" }, "sanitize": { + "title": "Sanitizza", + "desc": "Rimuovi elementi potenzialmente dannosi dai PDF.", "submit": "Pulire PDF", + "completed": "Sanitizzazione completata con successo", + "error.generic": "Sanitizzazione non riuscita", + "error.failed": "Si è verificato un errore durante la sanitizzazione del PDF.", + "filenamePrefix": "sanitizzato", + "sanitizationResults": "Risultati sanitizzazione", "steps": { - "settings": "Impostazioni" + "files": "File", + "settings": "Impostazioni", + "results": "Risultati" + }, + "files": { + "placeholder": "Seleziona un file PDF nella vista principale per iniziare" + }, + "options": { + "title": "Opzioni di sanitizzazione", + "note": "Seleziona gli elementi che vuoi rimuovere dal PDF. È necessario selezionarne almeno uno.", + "removeJavaScript.desc": "Rimuovi azioni e script JavaScript dal PDF", + "removeEmbeddedFiles.desc": "Rimuovi eventuali file incorporati nel PDF", + "removeXMPMetadata.desc": "Rimuovi i metadati XMP dal PDF", + "removeMetadata.desc": "Rimuovi le informazioni (titolo, autore, ecc.)", + "removeLinks.desc": "Rimuovi link esterni e azioni di avvio dal PDF", + "removeFonts.desc": "Rimuovi i font incorporati dal PDF" + } + }, + "addPassword": { + "title": "Aggiungi Password", + "desc": "Cifra il tuo documento PDF con una password.", + "completed": "Protezione con password applicata", + "submit": "Crittografa", + "filenamePrefix": "crittografato", + "error": { + "failed": "Si è verificato un errore durante la cifratura del PDF." + }, + "passwords": { + "stepTitle": "Password e Cifratura", + "completed": "Password configurate", + "user": { + "label": "Password Utente", + "placeholder": "Inserisci la password dell’utente" + }, + "owner": { + "label": "Password Proprietario", + "placeholder": "Inserisci la password del proprietario" + } + }, + "encryption": { + "keyLength": { + "label": "Lunghezza Chiave di Cifratura", + "40bit": "40-bit (Bassa)", + "128bit": "128-bit (Standard)", + "256bit": "256-bit (Alta)" + } + }, + "results": { + "title": "PDF Cifrati" + }, + "tooltip": { + "header": { + "title": "Panoramica Protezione con Password" + }, + "passwords": { + "title": "Tipi di Password", + "text": "Le password utente limitano l’apertura del documento, mentre le password proprietario controllano cosa è possibile fare con il documento una volta aperto. Puoi impostarne una o entrambe.", + "bullet1": "Password Utente: Necessaria per aprire il PDF", + "bullet2": "Password Proprietario: Controlla i permessi del documento (non supportato da tutti i visualizzatori PDF)" + }, + "encryption": { + "title": "Livelli di Cifratura", + "text": "Livelli di cifratura più alti offrono maggiore sicurezza ma potrebbero non essere supportati dai visualizzatori PDF più datati.", + "bullet1": "40-bit: Sicurezza di base, compatibile con visualizzatori più vecchi", + "bullet2": "128-bit: Sicurezza standard, ampiamente supportata", + "bullet3": "256-bit: Massima sicurezza, richiede visualizzatori moderni" + }, + "permissions": { + "title": "Cambia Permessi", + "text": "Questi permessi controllano ciò che gli utenti possono fare con il PDF. Sono più efficaci combinati con una password proprietario." + } } }, "changePermissions": { "title": "Cambia Permessi", + "desc": "Modifica restrizioni e permessi del documento.", + "completed": "Permessi modificati", "submit": "Cambia Permessi", + "error": { + "failed": "Si è verificato un errore durante il cambio dei permessi del PDF." + }, "permissions": { "preventAssembly": { "label": "Previeni assemblaggio del documento" @@ -1743,10 +3116,183 @@ "label": "Previeni stampa in diversi formati" } }, + "results": { + "title": "PDF modificati" + }, "tooltip": { "header": { "title": "Cambia Permessi" + }, + "description": { + "text": "Modifica i permessi del documento, consentendo/vietando l’accesso a diverse funzionalità nei lettori PDF." + }, + "warning": { + "text": "Per rendere questi permessi immodificabili, usa lo strumento Aggiungi password per impostare una password proprietario." } } - } + }, + "removePassword": { + "title": "Rimuovi Password", + "desc": "Rimuovi la password dal tuo PDF.", + "tags": "Decriptare,proteggere,rimuovere la password,eliminare la password", + "password": { + "stepTitle": "Rimuovi Password", + "label": "Password attuale", + "placeholder": "Inserisci la password attuale", + "completed": "Password configurata" + }, + "filenamePrefix": "decrittografato", + "error": { + "failed": "Si è verificato un errore durante la rimozione della password dal PDF." + }, + "tooltip": { + "description": "La rimozione della protezione con password richiede la password usata per cifrare il PDF. Questo decifrerà il documento, rendendolo accessibile senza password." + }, + "submit": "Rimuovi Password", + "results": { + "title": "PDF decrittografati" + } + }, + "automate": { + "title": "Automatizza", + "desc": "Crea flussi di lavoro multi‑step concatenando azioni PDF. Ideale per attività ricorrenti.", + "invalidStep": "Passo non valido", + "files": { + "placeholder": "Seleziona i file da elaborare con questa automazione" + }, + "selection": { + "title": "Selezione Automazione", + "saved": { + "title": "Salvati" + }, + "createNew": { + "title": "Crea nuova automazione" + }, + "suggested": { + "title": "Suggeriti" + } + }, + "creation": { + "createTitle": "Crea Automazione", + "editTitle": "Modifica Automazione", + "intro": "Le automazioni eseguono gli strumenti in sequenza. Per iniziare, aggiungi gli strumenti nell’ordine in cui vuoi che vengano eseguiti.", + "name": { + "label": "Nome Automazione", + "placeholder": "La mia automazione" + }, + "description": { + "label": "Descrizione (opzionale)", + "placeholder": "Descrivi cosa fa questa automazione..." + }, + "tools": { + "selectTool": "Seleziona uno strumento...", + "selected": "Strumenti selezionati", + "remove": "Rimuovi strumento", + "configure": "Configura strumento", + "notConfigured": "! Non configurato", + "addTool": "Aggiungi strumento", + "add": "Aggiungi uno strumento..." + }, + "save": "Salva automazione", + "unsavedChanges": { + "title": "Modifiche non salvate", + "message": "Hai modifiche non salvate. Sei sicuro di voler tornare indietro? Tutte le modifiche andranno perse.", + "cancel": "Annulla", + "confirm": "Torna indietro" + }, + "icon": { + "label": "Icona" + } + }, + "run": { + "title": "Esegui automazione" + }, + "sequence": { + "unnamed": "Automazione senza nome", + "steps": "{{count}} passaggi", + "running": "Esecuzione automazione...", + "run": "Esegui automazione", + "finish": "Fine" + }, + "reviewTitle": "Risultati Automazione", + "config": { + "loading": "Caricamento configurazione strumento...", + "noSettings": "Questo strumento non ha impostazioni configurabili.", + "title": "Configura {{toolName}}", + "description": "Configura le impostazioni per questo strumento. Queste impostazioni verranno applicate quando l’automazione verrà eseguita.", + "cancel": "Annulla", + "save": "Salva configurazione" + }, + "copyToSaved": "Copia in Salvati" + }, + "automation": { + "suggested": { + "securePdfIngestion": "Acquisizione PDF Sicura", + "securePdfIngestionDesc": "Flusso completo di elaborazione PDF che sanifica i documenti, applica OCR con pulizia, converte in formato PDF/A per archiviazione a lungo termine e ottimizza la dimensione del file.", + "emailPreparation": "Preparazione Email", + "emailPreparationDesc": "Ottimizza i PDF per la distribuzione via email comprimendo i file, dividendo i documenti grandi in parti da 20MB compatibili con l’email e rimuovendo i metadati per la privacy.", + "secureWorkflow": "Flusso di Sicurezza", + "secureWorkflowDesc": "Protegge i documenti PDF rimuovendo contenuti potenzialmente malevoli come JavaScript e file incorporati, quindi aggiunge la protezione con password per impedire accessi non autorizzati. La password predefinita è 'password'.", + "processImages": "Elabora Immagini", + "processImagesDesc": "Converte più file immagine in un unico documento PDF, quindi applica la tecnologia OCR per estrarre testo ricercabile dalle immagini." + } + }, + "common": { + "copy": "Copia", + "copied": "Copiato!", + "refresh": "Aggiorna", + "retry": "Riprova", + "remaining": "rimanenti", + "used": "usati", + "available": "disponibili", + "cancel": "Annulla" + }, + "config": { + "account": { + "overview": { + "title": "Impostazioni account", + "manageAccountPreferences": "Gestisci le preferenze del tuo account", + "guestDescription": "Sei connesso come ospite. Considera l’aggiornamento dell’account qui sopra." + }, + "upgrade": { + "title": "Aggiorna account ospite", + "description": "Collega il tuo account per preservare la cronologia e accedere a più funzionalità!", + "socialLogin": "Aggiorna con account social", + "linkWith": "Collega con", + "emailPassword": "oppure inserisci la tua email e password", + "email": "Email", + "emailPlaceholder": "Inserisci la tua email", + "password": "Password (opzionale)", + "passwordPlaceholder": "Imposta una password", + "passwordNote": "Lascia vuoto per usare solo la verifica via email", + "upgradeButton": "Aggiorna account" + } + }, + "apiKeys": { + "description": "La tua chiave API per accedere alla suite di strumenti PDF di Stirling. Copiala nel tuo progetto o aggiorna per generarne una nuova.", + "publicKeyAriaLabel": "Chiave API pubblica", + "copyKeyAriaLabel": "Copia chiave API", + "refreshAriaLabel": "Aggiorna chiave API", + "includedCredits": "Crediti inclusi", + "purchasedCredits": "Crediti acquistati", + "totalCredits": "Crediti totali", + "chartAriaLabel": "Uso crediti: inclusi {{includedUsed}} di {{includedTotal}}, acquistati {{purchasedUsed}} di {{purchasedTotal}}", + "nextReset": "Prossimo reset", + "lastApiUse": "Ultimo uso API", + "overlayMessage": "Genera una chiave per vedere crediti e crediti disponibili", + "label": "Chiave API", + "guestInfo": "Gli utenti ospiti non ricevono chiavi API. Crea un account per ottenere una chiave API da usare nelle tue applicazioni.", + "goToAccount": "Vai all’Account", + "refreshModal": { + "title": "Aggiorna chiavi API", + "warning": "⚠️ Attenzione: questa azione genererà nuove chiavi API e renderà non valide le chiavi precedenti.", + "impact": "Qualsiasi applicazione o servizio che usa attualmente queste chiavi smetterà di funzionare finché non le aggiornerai con le nuove.", + "confirmPrompt": "Sei sicuro di voler continuare?", + "confirmCta": "Aggiorna chiavi" + }, + "generateError": "Non siamo riusciti a generare la tua chiave API." + } + }, + "termsAndConditions": "Termini e condizioni", + "logOut": "Esci" } \ No newline at end of file diff --git a/frontend/public/locales/zh-CN/translation.json b/frontend/public/locales/zh-CN/translation.json index 66586d854..52367f112 100644 --- a/frontend/public/locales/zh-CN/translation.json +++ b/frontend/public/locales/zh-CN/translation.json @@ -35,10 +35,28 @@ "true": "对", "false": "错", "unknown": "未知", + "app": { + "description": "免费的 Adobe Acrobat 替代品(下载量 1000 万+)" + }, "save": "保存", "saveToBrowser": "保存到浏览器", + "download": "下载", + "undoOperationTooltip": "点击撤销上一次操作并还原原始文件", + "undo": "撤销", + "moreOptions": "更多选项", + "editYourNewFiles": "编辑您的新文件", "close": "关闭", + "fileSelected": "已选:{{filename}}", + "chooseFile": "选择文件", "filesSelected": "选中的文件", + "files": { + "title": "文件", + "upload": "上传", + "uploadFiles": "上传文件", + "addFiles": "添加文件", + "selectFromWorkbench": "从工作台中选择文件或 ", + "selectMultipleFromWorkbench": "从工作台中至少选择 {{count}} 个文件或 " + }, "noFavourites": "没有添加收藏夹", "downloadComplete": "下载完成", "bored": "等待时觉得无聊?", @@ -71,6 +89,10 @@ "githubSubmit": "GitHub - 提交工单", "discordSubmit": "Discord - 提交支持帖子" }, + "warning": { + "tooltipTitle": "警告" + }, + "edit": "编辑", "delete": "删除", "username": "用户名", "password": "密码", @@ -82,6 +104,7 @@ "green": "绿色", "blue": "蓝色", "custom": "自定义...", + "comingSoon": "即将推出", "WorkInProgess": "工作正在进行中,可能无法工作或有错误,请报告任何问题!", "poweredBy": "服务来源:", "yes": "是", @@ -115,12 +138,14 @@ "page": "页面", "pages": "页码", "loading": "加载中...", + "review": "审核", "addToDoc": "添加至文件", "reset": "重置", "apply": "应用", "noFileSelected": "未选择文件,请上传一个文件。", "legal": { "privacy": "隐私政策", + "iAgreeToThe": "我同意所有", "terms": "服务条款", "accessibility": "无障碍", "cookie": "Cookie 政策", @@ -347,13 +372,9 @@ "title": "旋转", "desc": "旋转 PDF。" }, - "imageToPDF": { - "title": "转换图像到 PDF", - "desc": "将图像(PNG、JPEG、GIF)转换为 PDF。" - }, - "pdfToImage": { - "title": "转换 PDF 到图像", - "desc": "将 PDF 转换为图像(PNG、JPEG、GIF)。" + "convert": { + "title": "转换", + "desc": "在不同格式之间转换文件" }, "pdfOrganiser": { "title": "整理", @@ -363,22 +384,14 @@ "title": "在 PDF 中添加图片", "desc": "将图像添加到 PDF 的指定位置。" }, + "addAttachments": { + "title": "添加附件", + "desc": "向 PDF 添加或移除嵌入文件(附件)" + }, "watermark": { "title": "添加水印", "desc": "在 PDF 中添加自定义水印。" }, - "permissions": { - "title": "更改权限", - "desc": "更改 PDF 文档的权限。" - }, - "pageRemover": { - "title": "删除", - "desc": "从 PDF 文档中删除不需要的页面。" - }, - "addPassword": { - "title": "添加密码", - "desc": "使用密码对 PDF 文档进行加密。" - }, "removePassword": { "title": "删除密码", "desc": "从 PDF 文档中移除密码保护。" @@ -395,10 +408,6 @@ "title": "更改元数据", "desc": "更改/删除/添加 PDF 文档的元数据。" }, - "fileToPDF": { - "title": "将文件转换为 PDF 文件", - "desc": "将几乎所有文件转换为 PDF (DOCX、PNG、XLS、PPT、TXT等)。" - }, "ocr": { "title": "运行 OCR /清理扫描", "desc": "清理和识别 PDF 中的图像文本,并将其转换为可编辑文本。" @@ -407,33 +416,9 @@ "title": "提取图像", "desc": "从 PDF 中提取所有图像并保存到压缩包中。" }, - "pdfToPDFA": { - "title": "PDF 转 PDF/A", - "desc": "将 PDF 转换为 PDF/A 以进行长期保存。" - }, - "PDFToWord": { - "title": "PDF 转 Word", - "desc": "将PDF转换为Word格式(DOC、DOCX和ODT)。" - }, - "PDFToPresentation": { - "title": "PDF 转演示文稿", - "desc": "将 PDF 转换为演示文稿格式(PPT、PPTX 和 ODP)。" - }, - "PDFToText": { - "title": "PDF 转 RTF(文本)", - "desc": "将PDF转换为文本或 RTF 格式。" - }, - "PDFToHTML": { - "title": "PDF 转 HTML", - "desc": "将 PDF 转换为 HTML 格式。" - }, - "PDFToXML": { - "title": "PDF 转 XML", - "desc": "将 PDF 转换为 XML 格式。" - }, - "ScannerImageSplit": { - "title": "检测/分割扫描图像", - "desc": "从一张照片或 PDF 中分割出多张照片。" + "scannerImageSplit": { + "title": "检测/拆分扫描照片", + "desc": "从照片/PDF 中拆分出多张照片" }, "sign": { "title": "签名", @@ -443,6 +428,10 @@ "title": "展平", "desc": "从 PDF 中删除所有互动元素和表单" }, + "certSign": { + "title": "使用证书签名", + "desc": "使用证书/密钥(PEM/P12)对PDF进行签名" + }, "repair": { "title": "修复", "desc": "尝试修复损坏/损坏的 PDF" @@ -459,10 +448,6 @@ "title": "比较", "desc": "比较并显示两个 PDF 文档之间的差异" }, - "certSign": { - "title": "使用证书签名", - "desc": "使用证书/密钥(PEM/P12)对PDF进行签名" - }, "removeCertSign": { "title": "移除证书签名", "desc": "移除 PDF 的证书签名" @@ -471,21 +456,21 @@ "title": "多页布局", "desc": "将 PDF 文档的多个页面合并成一页" }, + "bookletImposition": { + "title": "小册子拼版", + "desc": "创建具有正确页面顺序和多页布局的小册子,用于打印和装订" + }, "scalePages": { "title": "调整页面尺寸/缩放", "desc": "调整页面及/或其内容的尺寸/缩放" }, - "pipeline": { - "title": "流水线(高级版)", - "desc": "通过定义流水线脚本在 PDF 上运行多个操作" - }, "addPageNumbers": { "title": "添加页码", "desc": "在文档的指定位置添加页码" }, - "auto-rename": { + "autoRename": { "title": "自动重命名 PDF 文件", - "desc": "根据检测到的标题自动对 PDF 文件进行重命名" + "desc": "基于检测到的页眉自动重命名 PDF 文件" }, "adjustContrast": { "title": "调整颜色/对比度", @@ -499,34 +484,14 @@ "title": "自动拆分页面", "desc": "使用物理扫描页面分割器 QR 代码自动拆分扫描的 PDF" }, - "sanitizePDF": { - "title": "清理", - "desc": "从 PDF 文件中删除脚本和其他元素" - }, - "URLToPDF": { - "title": "URL/网站转 PDF", - "desc": "将任何 http(s)URL 转换为PDF" - }, - "HTMLToPDF": { - "title": "HTML 转 PDF", - "desc": "将任何 HTML 文件或 zip 文件转换为 PDF" - }, - "MarkdownToPDF": { - "title": "Markdown 转 PDF", - "desc": "将任何 Markdown 文件转换为 PDF" - }, - "PDFToMarkdown": { - "title": "PDF 转 Markdown", - "desc": "将任何pdf文件转换为Markdown文件" + "sanitize": { + "title": "安全清理", + "desc": "移除 PDF 文件中的潜在有害元素" }, "getPdfInfo": { "title": "获取 PDF 的所有信息", "desc": "获取 PDF 的所有可能的信息" }, - "pageExtracter": { - "title": "提取页面", - "desc": "从 PDF 中提取选定的页面" - }, "pdfToSinglePage": { "title": "PDF 转单一大页", "desc": "将所有 PDF 页面合并为一个大的单页" @@ -535,33 +500,21 @@ "title": "显示 JavaScript", "desc": "搜索并显示嵌入到 PDF 中的任何 JavaScript 代码" }, - "autoRedact": { - "title": "自动删除", - "desc": "根据输入文本自动删除(覆盖)PDF 中的文本" - }, "redact": { "title": "手动修订", "desc": "根据选定的文本、绘制的形状和/或选定的页面编辑PDF" }, - "PDFToCSV": { - "title": "PDF 转 CSV", - "desc": "从 PDF 中提取表格并将其转换为 CSV" - }, - "split-by-size-or-count": { - "title": "自动根据大小/数目拆分 PDF", - "desc": "将单个 PDF 拆分为多个文档,基于大小、页数或文档数" - }, - "overlay-pdfs": { + "overlayPdfs": { "title": "叠加 PDF", - "desc": "将 PDF 叠加在另一个 PDF 上" + "desc": "将一个 PDF 叠加到另一个 PDF 之上" }, - "split-by-sections": { - "title": "拆分 PDF 成小块", - "desc": "将 PDF 的每一页分割成更小的水平和垂直的部分" + "splitBySections": { + "title": "按区块拆分 PDF", + "desc": "将 PDF 的每一页分割为更小的横向与纵向区块" }, - "AddStampRequest": { - "title": "添加图章", - "desc": "在指定位置添加文本或图片图章" + "addStamp": { + "title": "向 PDF 添加印章", + "desc": "在指定位置添加文本或图像印章" }, "removeImage": { "title": "删除图像", @@ -575,48 +528,79 @@ "title": "验证 PDF 签名", "desc": "验证 PDF 文档中的数字签名和证书" }, - "replace-color": { - "title": "替换和反转颜色", - "desc": "替换 PDF 中文本和背景的颜色,并将PDF全色反转以减小文件大小" + "swagger": { + "title": "API 文档", + "desc": "查看 API 文档并测试端点" }, - "convert": { - "title": "转换" - }, - "attachments": { - "title": "添加附件", - "desc": "在 PDF 中添加或删除嵌入文件(附件)" + "fakeScan": { + "title": "伪扫描", + "desc": "创建看起来像扫描件的 PDF" }, "editTableOfContents": { "title": "编辑目录", "desc": "为 PDF 文档添加或编辑目录和书签" }, + "manageCertificates": { + "title": "管理证书", + "desc": "导入、导出或删除用于签名 PDF 的数字证书文件。" + }, + "read": { + "title": "阅读", + "desc": "查看与批注 PDF。高亮、绘制或插入评论以便审阅协作。" + }, + "reorganizePages": { + "title": "重组页面", + "desc": "通过可视化拖放控制重新排列、复制或删除 PDF 页面。" + }, "extractPages": { - "title": "提取页面" + "title": "提取页面", + "desc": "从 PDF 文档中提取特定页面" }, "removePages": { "title": "删除", "desc": "从 PDF 文档中删除不需要的页面。" }, - "removeImagePdf": { - "title": "删除图像", - "desc": "删除图像减少 PDF 大小" - }, "autoSizeSplitPDF": { "title": "自动根据大小/数目拆分 PDF", "desc": "将单个 PDF 拆分为多个文档,基于大小、页数或文档数" }, - "adjust-contrast": { - "title": "调整颜色/对比度", - "desc": "调整 PDF 的对比度、饱和度和亮度" - }, "replaceColorPdf": { "title": "替换和反转颜色", "desc": "替换 PDF 中文本和背景的颜色,并将PDF反转颜色以减小文件大小" }, + "devApi": { + "title": "API", + "desc": "跳转至 API 文档" + }, + "devFolderScanning": { + "title": "自动文件夹扫描", + "desc": "跳转至自动文件夹扫描指南" + }, + "devSsoGuide": { + "title": "SSO 指南", + "desc": "跳转至 SSO 指南" + }, + "devAirgapped": { + "title": "离线/隔离部署", + "desc": "跳转至隔离部署指南" + }, + "addPassword": { + "title": "添加密码", + "desc": "使用密码对 PDF 文档进行加密。" + }, "changePermissions": { - "title": "更改权限" + "title": "更改权限", + "desc": "更改文档限制与权限" + }, + "automate": { + "title": "自动化", + "desc": "通过串联 PDF 操作构建多步工作流。适合重复性任务。" } }, + "landing": { + "addFiles": "添加文件", + "uploadFromComputer": "从电脑上传" + }, "viewPdf": { "tags": "浏览、阅读、注释、文本、图像", "title": "浏览/编辑 PDF", @@ -650,13 +634,26 @@ "merge": { "tags": "合并,页面操作,后端,服务器端", "title": "合并", - "header": "合并多个 PDF(2个以上)。", - "sortByName": "按名称排序", - "sortByDate": "按日期排序", - "removeCertSign": "删除合并文件的数字签名吗?", + "removeDigitalSignature.tooltip": { + "title": "移除数字签名", + "description": "合并文件时数字签名会失效。勾选此项以从最终合并的 PDF 中移除它们。" + }, + "generateTableOfContents.tooltip": { + "title": "生成目录", + "description": "根据原始文件名和页码,自动在合并后的 PDF 中创建可点击的目录。" + }, "submit": "合并", "sortBy": { - "filename": "文件名" + "description": "文件将按选择顺序合并。您可以拖动重新排序,或在下方进行排序。", + "label": "排序方式", + "filename": "文件名", + "dateModified": "修改日期", + "ascending": "升序", + "descending": "降序", + "sort": "排序" + }, + "error": { + "failed": "合并 PDF 时发生错误。" } }, "split": { @@ -676,25 +673,207 @@ "splitPages": "输入要分割的页面:", "submit": "拆分", "steps": { + "chooseMethod": "选择方式", "settings": "设置" }, + "settings": { + "selectMethodFirst": "请先选择一种拆分方式" + }, + "error": { + "failed": "拆分 PDF 时发生错误。" + }, + "method": { + "label": "选择拆分方式", + "placeholder": "选择如何拆分 PDF" + }, "methods": { + "prefix": { + "splitAt": "拆分于", + "splitBy": "拆分依据" + }, + "byPages": { + "name": "页码", + "desc": "提取特定页面(1,3,5-10)", + "tooltip": "输入用逗号分隔的页码或带连字符的范围" + }, + "bySections": { + "name": "区块", + "desc": "将页面划分为网格区块", + "tooltip": "将每一页拆分为横向与纵向的多个区块" + }, "bySize": { - "name": "文件大小" + "name": "文件大小", + "desc": "限制最大文件大小", + "tooltip": "指定最大文件大小(如 10MB、500KB)" + }, + "byPageCount": { + "name": "页数", + "desc": "每个文件固定页数", + "tooltip": "输入每个拆分文件的页数" + }, + "byDocCount": { + "name": "文件数量", + "desc": "创建指定数量的文件", + "tooltip": "输入您想创建的文件数量" + }, + "byChapters": { + "name": "章节", + "desc": "在书签边界处拆分", + "tooltip": "使用 PDF 书签来确定拆分位置" + }, + "byPageDivider": { + "name": "分隔页", + "desc": "使用分隔页自动拆分", + "tooltip": "扫描时在文档间放置带二维码的分隔页" } }, "value": { "fileSize": { - "label": "文件大小" + "label": "文件大小", + "placeholder": "例如:10MB,500KB" + }, + "pageCount": { + "label": "每个文件的页数", + "placeholder": "例如:5,10" + }, + "docCount": { + "label": "文件数量", + "placeholder": "例如:3,5" + } + }, + "tooltip": { + "header": { + "title": "拆分方式概览" + }, + "byPages": { + "title": "按页码拆分", + "text": "在指定页码处拆分 PDF。使用“n”表示在第 n 页之后拆分;使用“n-m”表示在第 n 页之前与第 m 页之后拆分。", + "bullet1": "单一拆分点:3,7(在第 3 与第 7 页后拆分)", + "bullet2": "范围拆分点:3-8(在第 3 页前与第 8 页后拆分)", + "bullet3": "混合:2,5-10,15(在第 2 页后、第 5 页前、第 10 页后与第 15 页后拆分)" + }, + "bySections": { + "title": "按网格区块拆分", + "text": "将每一页划分为网格区块。适用于多栏文档的拆分或提取特定区域。", + "bullet1": "水平:要创建的行数", + "bullet2": "垂直:要创建的列数", + "bullet3": "合并:将所有区块合并为一个 PDF" + }, + "bySize": { + "title": "按文件大小拆分", + "text": "创建多个不超过指定大小的 PDF。适用于大小限制或邮件附件。", + "bullet1": "较大文件使用 MB(如 10MB)", + "bullet2": "较小文件使用 KB(如 500KB)", + "bullet3": "系统会在页面边界进行拆分" + }, + "byCount": { + "title": "按数量拆分", + "text": "创建多个 PDF,每个包含特定页数或生成特定数量的文档。", + "bullet1": "页数:每个文件固定页数", + "bullet2": "文件数量:固定输出文件数量", + "bullet3": "适合批处理工作流" + }, + "byChapters": { + "title": "按章节拆分", + "text": "使用 PDF 书签在章节边界处自动拆分。需要 PDF 具有书签结构。", + "bullet1": "书签层级:在第几级书签处拆分(1=顶级)", + "bullet2": "包含元数据:保留文档属性", + "bullet3": "允许重复:处理重复的书签名称" } } }, "rotate": { "tags": "服务器端", "title": "旋转 PDF", - "header": "旋转 PDF", - "selectAngle": "选择旋转角度(以 90 度的倍数):", - "submit": "旋转" + "submit": "旋转", + "error": { + "failed": "旋转 PDF 时发生错误。" + }, + "preview": { + "title": "旋转预览" + }, + "rotateLeft": "逆时针旋转", + "rotateRight": "顺时针旋转", + "tooltip": { + "header": { + "title": "旋转设置概览" + }, + "description": { + "text": "以 90° 为增量顺时针或逆时针旋转 PDF 页面。PDF 中所有页面都会被旋转。预览会显示旋转后的效果。" + }, + "controls": { + "title": "控件", + "text": "使用旋转按钮调整方向。左键逆时针,右键顺时针。每次点击旋转 90°。" + } + } + }, + "convert": { + "title": "转换", + "desc": "在不同格式之间转换文件", + "files": "文件", + "selectFilesPlaceholder": "在主视图中选择文件以开始", + "settings": "设置", + "conversionCompleted": "转换完成", + "results": "结果", + "defaultFilename": "converted_file", + "conversionResults": "转换结果", + "convertFrom": "转换来源", + "convertTo": "转换为", + "sourceFormatPlaceholder": "源格式", + "targetFormatPlaceholder": "目标格式", + "selectSourceFormatFirst": "请先选择源格式", + "outputOptions": "输出选项", + "pdfOptions": "PDF 选项", + "imageOptions": "图像选项", + "colorType": "颜色类型", + "color": "颜色", + "greyscale": "灰度", + "blackwhite": "黑白", + "dpi": "DPI", + "output": "输出", + "single": "单个", + "multiple": "多个", + "fitOption": "适配选项", + "maintainAspectRatio": "保持纵横比", + "fitDocumentToPage": "适配页面", + "fillPage": "填充页面", + "autoRotate": "自动旋转", + "autoRotateDescription": "自动旋转图像以更好地适配 PDF 页面", + "combineImages": "合并图像", + "combineImagesDescription": "将所有图像合并为一个 PDF,或为每个图像创建单独的 PDF", + "webOptions": "网页转 PDF 选项", + "zoomLevel": "缩放级别", + "emailOptions": "邮件转 PDF 选项", + "includeAttachments": "包含邮件附件", + "maxAttachmentSize": "最大附件大小(MB)", + "includeAllRecipients": "在页眉中包含抄送(CC)与密送(BCC)收件人", + "downloadHtml": "下载 HTML 中间文件(而非 PDF)", + "pdfaOptions": "PDF/A 选项", + "outputFormat": "输出格式", + "pdfaNote": "PDF/A-1b 兼容性更好,PDF/A-2b 支持的功能更多。", + "pdfaDigitalSignatureWarning": "该PDF包含数字签名,下一步将移除该签名。", + "fileFormat": "文件格式", + "wordDoc": "Word 文档", + "wordDocExt": "Word 文档(.docx)", + "odtExt": "OpenDocument 文本(.odt)", + "pptExt": "PowerPoint(.pptx)", + "odpExt": "OpenDocument 演示文稿(.odp)", + "txtExt": "纯文本(.txt)", + "rtfExt": "富文本(.rtf)", + "selectedFiles": "已选文件", + "noFileSelected": "未选择文件。请使用文件面板添加文件。", + "convertFiles": "转换文件", + "converting": "正在转换…", + "downloadConverted": "下载转换后的文件", + "errorNoFiles": "请至少选择一个要转换的文件。", + "errorNoFormat": "请选择源格式和目标格式。", + "errorNotSupported": "不支持从 {{from}} 转换为 {{to}}。", + "images": "图像", + "officeDocs": "Office 文档(Word、Excel、PowerPoint)", + "imagesExt": "图像(JPG、PNG 等)", + "markdown": "Markdown", + "textRtf": "文本/RTF", + "grayscale": "灰度" }, "imageToPdf": { "tags": "转换、图像、JPG、图片、照片" @@ -744,33 +923,190 @@ "upload": "添加图片", "submit": "添加图片" }, + "attachments": { + "tags": "嵌入、附件、文件、附加", + "title": "添加附件", + "header": "添加附件", + "add": "添加附件", + "remove": "移除附件", + "embed": "嵌入附件", + "submit": "添加附件" + }, "watermark": { - "tags": "文本、重复、标签、自定义、版权、商标、图像、JPG、图片、照片", "title": "添加水印", - "header": "添加水印", - "customColor": "自定义文本颜色", - "selectText": { - "1": "选择要添加水印的 PDF:", - "2": "水印文本:", - "3": "字体大小:", - "4": "旋转(0-360):", - "5": "水平间距(每个水印之间的水平距离):", - "6": "垂直间距(每个水印之间的垂直距离):", - "7": "透明度(0% - 100%):", - "8": "水印类型:", - "9": "水印图片:", - "10": "将 PDF 转换为 PDF-Image" - }, + "desc": "向 PDF 添加文本或图像水印", + "completed": "已添加水印", "submit": "添加水印", - "type": { - "1": "文字", - "2": "图片" + "filenamePrefix": "watermarked", + "error": { + "failed": "向 PDF 添加水印时发生错误。" }, "watermarkType": { - "text": "文本" + "text": "文本", + "image": "图像" }, "settings": { - "fontSize": "字体大小" + "type": "水印类型", + "text": { + "label": "水印文本", + "placeholder": "输入水印文本" + }, + "image": { + "label": "水印图像", + "choose": "选择图像", + "selected": "已选:{{filename}}" + }, + "fontSize": "字体大小", + "size": "大小", + "alphabet": "字体/语言", + "color": "水印颜色", + "rotation": "旋转(度)", + "opacity": "不透明度(%)", + "spacing": { + "horizontal": "水平间距", + "vertical": "垂直间距" + }, + "convertToImage": "将 PDF 页面转为图像" + }, + "alphabet": { + "roman": "罗马/拉丁", + "arabic": "阿拉伯语", + "japanese": "日语", + "korean": "韩语", + "chinese": "中文", + "thai": "泰语" + }, + "steps": { + "type": "水印类型", + "wording": "措辞", + "textStyle": "样式", + "formatting": "格式", + "file": "水印文件" + }, + "results": { + "title": "水印结果" + }, + "tooltip": { + "language": { + "title": "语言支持", + "text": "选择合适的语言设置以确保文本的正确字体呈现。" + }, + "appearance": { + "title": "外观设置", + "text": "控制水印的外观及与文档的融合效果。", + "bullet1": "旋转:-360° 至 360°,可创建倾斜水印", + "bullet2": "不透明度:0-100% 控制透明度", + "bullet3": "较低不透明度可获得更柔和的水印" + }, + "spacing": { + "title": "间距控制", + "text": "调整页面上重复水印之间的间距。", + "bullet1": "宽度间距:水平方向的距离", + "bullet2": "高度间距:垂直方向的距离", + "bullet3": "数值越大,图案越稀疏" + }, + "type": { + "header": { + "title": "水印类型选择" + }, + "description": { + "title": "选择您的水印类型", + "text": "根据需求在文本水印与图像水印之间进行选择。" + }, + "text": { + "title": "文本水印", + "text": "非常适合添加版权声明、公司名称或保密标记。支持多语言与自定义颜色。", + "bullet1": "可自定义字体与语言", + "bullet2": "可调整颜色与透明度", + "bullet3": "适用于法律声明或品牌文字" + }, + "image": { + "title": "图像水印", + "text": "使用徽标、印章或任意图像作为水印。非常适合品牌与视觉识别。", + "bullet1": "上传任意图像格式", + "bullet2": "保持图像质量", + "bullet3": "非常适合徽标与印章" + } + }, + "wording": { + "header": { + "title": "文本内容" + }, + "text": { + "title": "水印文本", + "text": "输入将作为水印显示在整个文档上的文本。", + "bullet1": "保持简洁以提升可读性", + "bullet2": "常见示例:“CONFIDENTIAL”“DRAFT”、公司名称", + "bullet3": "不支持表情符号字符,会被过滤" + } + }, + "textStyle": { + "header": { + "title": "文本样式" + }, + "color": { + "title": "颜色选择", + "text": "选择与文档内容形成良好对比的颜色。", + "bullet1": "浅灰(#d3d3d3)用于柔和水印", + "bullet2": "黑色或深色用于高对比度", + "bullet3": "自定义颜色用于品牌诉求" + }, + "language": { + "title": "语言支持", + "text": "选择合适的语言设置以确保正确的字体渲染。" + } + }, + "file": { + "header": { + "title": "图像上传" + }, + "upload": { + "title": "选择图像", + "text": "上传一张图像作为水印。", + "bullet1": "支持常见格式:PNG、JPG、GIF、BMP", + "bullet2": "带透明背景的 PNG 效果最佳", + "bullet3": "更高分辨率可更好地保持质量" + }, + "recommendations": { + "title": "最佳实践", + "text": "获得最佳图像水印效果的提示。", + "bullet1": "使用带透明背景的徽标或印章", + "bullet2": "简单设计优于复杂图形", + "bullet3": "选择分辨率时请考虑最终文档大小" + } + }, + "formatting": { + "header": { + "title": "格式与布局" + }, + "size": { + "title": "大小控制", + "text": "调整水印(文本或图像)的大小。", + "bullet1": "更大的尺寸会使水印更显眼" + }, + "appearance": { + "title": "外观设置", + "text": "控制水印的外观及与文档的融合效果。", + "bullet1": "旋转:-360° 至 360°,用于倾斜水印", + "bullet2": "不透明度:0-100% 控制透明度", + "bullet3": "较低不透明度可获得更柔和的水印" + }, + "spacing": { + "title": "间距控制", + "text": "调整页面上重复水印之间的间距。", + "bullet1": "水平间距:水印左右间的距离", + "bullet2": "垂直间距:水印上下间的距离", + "bullet3": "更高数值会使图案更稀疏" + }, + "security": { + "title": "安全选项", + "text": "将最终 PDF 转换为基于图像的格式以增强安全性。", + "bullet1": "防止文本选择与复制", + "bullet2": "使水印更难移除", + "bullet3": "会导致文件体积增大", + "bullet4": "适用于敏感或受版权保护的内容" + } + } } }, "permissions": { @@ -795,50 +1131,148 @@ "removePages": { "tags": "删除页面、删除", "title": "删除", + "pageNumbers": { + "label": "要移除的页面", + "placeholder": "例如:1,3,5-8,10", + "error": "无效的页码格式。使用数字、范围(1-5)或数学表达式(2n+1)" + }, + "filenamePrefix": "pages_removed", + "files": { + "placeholder": "在主视图中选择一个 PDF 文件以开始" + }, + "settings": { + "title": "设置" + }, + "tooltip": { + "header": { + "title": "移除页面设置" + }, + "pageNumbers": { + "title": "页面选择", + "text": "指定要从 PDF 中移除的页面。您可以选择单个页、范围,或使用数学表达式。", + "bullet1": "单个页面:1,3,5(移除第 1、3、5 页)", + "bullet2": "页码范围:1-5,10-15(移除第 1-5 与 10-15 页)", + "bullet3": "数学表达:2n+1(移除奇数页)", + "bullet4": "开放范围:5-(移除从第 5 页至末尾)" + }, + "examples": { + "title": "常见示例", + "text": "以下是一些常见的页面选择模式:", + "bullet1": "移除第一页:1", + "bullet2": "移除最后 3 页:-3", + "bullet3": "每隔一页移除:2n", + "bullet4": "移除散落的特定页:1,5,10,15" + }, + "safety": { + "title": "安全提示", + "text": "移除页面时的重要注意事项:", + "bullet1": "处理前请始终预览您的选择", + "bullet2": "保留原文件的备份", + "bullet3": "页码从 1 开始而非 0", + "bullet4": "无效页码将被忽略" + } + }, + "error": { + "failed": "移除页面时发生错误。" + }, + "results": { + "title": "移除页面结果" + }, "submit": "删除" }, - "addPassword": { - "tags": "安全、密码、加密", - "title": "添加密码", - "header": "添加密码(加密)。", - "selectText": { - "1": "选择要加密的 PDF。", - "2": "密码", - "3": "加密密钥长度", - "4": "值越高越强,但值越低兼容性越好。", - "5": "要设置的权限", - "6": "防止文件的拼接。", - "7": "防止内容提取", - "8": "防止为可访问性提取内容", - "9": "防止填写表格", - "10": "防止修改", - "11": "防止修改注释", - "12": "防止打印", - "13": "防止打印不同的格式", - "14": "所有者密码", - "15": "限制打开后对文档的操作(不被所有阅读器支持)", - "16": "限制打开文档本身" - }, - "submit": "加密", + "pageSelection": { "tooltip": { - "permissions": { - "title": "更改权限" + "header": { + "title": "页面选择指南" + }, + "basic": { + "title": "基础用法", + "text": "使用简单语法从 PDF 文档中选择特定页面。", + "bullet1": "单页:1,3,5", + "bullet2": "范围:3-6 或 10-15", + "bullet3": "全部:all" + }, + "advanced": { + "title": "高级功能" + }, + "tips": { + "title": "提示", + "text": "请牢记以下指南:", + "bullet1": "页码从 1 开始(不是 0)", + "bullet2": "空格会被自动移除", + "bullet3": "无效表达式会被忽略" + }, + "syntax": { + "title": "语法基础", + "text": "使用数字、范围、关键字和等差表达式(n 从 0 开始)。支持括号。", + "bullets": { + "numbers": "数字/范围:5,10-20", + "keywords": "关键字:odd、even", + "progressions": "等差:3n,4n+1" + } + }, + "operators": { + "title": "运算符", + "text": "AND 优先级高于逗号。NOT 在文档范围内生效。", + "and": "AND:& 或 “and” — 同时满足(如 1-50 & even)", + "comma": "逗号:, 或 | — 合并选择(如 1-10, 20)", + "not": "NOT:! 或 “not” — 排除(如 3n & not 30)" + }, + "examples": { + "title": "示例" } } }, - "removePassword": { - "tags": "安全、解密、密码、安全性、删除密码", - "title": "删除密码", - "header": "移除密码(解密)。", - "selectText": { - "1": "选择要解密的 PDF", - "2": "密码" + "bulkSelection": { + "header": { + "title": "页面选择指南" }, - "submit": "删除", - "desc": "从 PDF 文档中移除密码保护。", - "password": { - "stepTitle": "删除密码", - "label": "当前密码" + "syntax": { + "title": "语法基础", + "text": "使用数字、范围、关键字与等差表达式(n 从 0 开始)。支持括号。", + "bullets": { + "numbers": "数字/范围:5,10-20", + "keywords": "关键字:odd、even", + "progressions": "等差:3n,4n+1" + } + }, + "operators": { + "title": "运算符", + "text": "AND 优先级高于逗号。NOT 在文档范围内生效。", + "and": "AND:& 或 “and” — 同时满足(如 1-50 & even)", + "comma": "逗号:, 或 | — 合并选择(如 1-10, 20)", + "not": "NOT:! 或 “not” — 排除(如 3n & not 30)" + }, + "examples": { + "title": "示例", + "first50": "前 50 页", + "last50": "后 50 页", + "every3rd": "每隔 3 页", + "oddWithinExcluding": "1-20 内的奇数页,排除 5-7", + "combineSets": "合并集合" + }, + "firstNPages": { + "title": "前 N 页", + "placeholder": "页数" + }, + "lastNPages": { + "title": "后 N 页", + "placeholder": "页数" + }, + "everyNthPage": { + "title": "每 N 页", + "placeholder": "步长" + }, + "range": { + "title": "范围", + "fromPlaceholder": "起始", + "toPlaceholder": "结束" + }, + "keywords": { + "title": "关键字" + }, + "advanced": { + "title": "高级" } }, "compressPdfs": { @@ -848,28 +1282,142 @@ "tags": "移除,删除,表单,字段,只读", "title": "移除表单字段只读属性", "header": "解锁 PDF 表单", - "submit": "Remove" + "submit": "Remove", + "description": "该工具将移除 PDF 表单字段的只读限制,使其可编辑、可填写。", + "filenamePrefix": "unlocked_forms", + "files": { + "placeholder": "在主视图中选择一个 PDF 文件以开始" + }, + "error": { + "failed": "解锁 PDF 表单时发生错误。" + }, + "results": { + "title": "表单解锁结果" + } }, "changeMetadata": { "tags": "标题、作者、日期、创建、时间、发布者、制作人、统计数据", - "title": "标题:", "header": "更改元数据", - "selectText": { - "1": "请编辑你想要改变的变量。", - "2": "删除所有元数据", - "3": "显示自定义元数据:", - "4": "其他元数据:", - "5": "添加自定义元数据条目" + "submit": "更改", + "filenamePrefix": "metadata", + "settings": { + "title": "元数据设置" }, - "author": "作者:", - "creationDate": "创建日期(yyyy/MM/dd HH:mm:ss):", - "creator": "创建者:", - "keywords": "关键词:", - "modDate": "修改日期(yyyy/MM/dd HH:mm:ss):", - "producer": "生产者:", - "subject": "主题:", - "trapped": "被困:", - "submit": "更改" + "standardFields": { + "title": "标准字段" + }, + "deleteAll": { + "label": "移除现有元数据", + "checkbox": "删除所有元数据" + }, + "title": { + "label": "标题", + "placeholder": "文档标题" + }, + "author": { + "label": "作者", + "placeholder": "文档作者" + }, + "subject": { + "label": "主题", + "placeholder": "文档主题" + }, + "keywords": { + "label": "关键字", + "placeholder": "文档关键字" + }, + "creator": { + "label": "创建者", + "placeholder": "文档创建者" + }, + "producer": { + "label": "生产者", + "placeholder": "文档生产者" + }, + "dates": { + "title": "日期字段" + }, + "creationDate": { + "label": "创建日期", + "placeholder": "创建日期" + }, + "modificationDate": { + "label": "修改日期", + "placeholder": "修改日期" + }, + "trapped": { + "label": "陷印状态", + "unknown": "Unknown", + "true": "True", + "false": "False" + }, + "advanced": { + "title": "高级选项" + }, + "customFields": { + "title": "自定义元数据", + "description": "向文档添加自定义元数据字段", + "add": "添加字段", + "key": "键", + "keyPlaceholder": "自定义键", + "value": "值", + "valuePlaceholder": "自定义值", + "remove": "移除" + }, + "results": { + "title": "已更新的 PDF" + }, + "error": { + "failed": "更改 PDF 元数据时发生错误。" + }, + "tooltip": { + "header": { + "title": "PDF 元数据概览" + }, + "standardFields": { + "title": "标准字段", + "text": "描述文档的常见 PDF 元数据字段。", + "bullet1": "标题:文档名称或题名", + "bullet2": "作者:创建文档的人", + "bullet3": "主题:对内容的简要描述", + "bullet4": "关键字:文档的搜索词", + "bullet5": "创建者/生产者:用于创建 PDF 的软件" + }, + "dates": { + "title": "日期字段", + "text": "文档的创建与修改时间。", + "bullet1": "创建日期:文档最初创建的时间", + "bullet2": "修改日期:文档上次更改的时间" + }, + "options": { + "title": "附加选项", + "text": "自定义字段与隐私控制。", + "bullet1": "自定义元数据:添加自定义键值对", + "bullet2": "陷印状态:高质量印刷设置", + "bullet3": "全部删除:为隐私移除所有元数据" + }, + "deleteAll": { + "title": "移除现有元数据", + "text": "完全删除元数据以确保隐私。" + }, + "customFields": { + "title": "自定义元数据", + "text": "添加您自己的自定义键值对元数据。", + "bullet1": "添加与文档相关的任何自定义字段", + "bullet2": "示例:部门、项目、版本、状态", + "bullet3": "每个条目都需要键和值" + }, + "advanced": { + "title": "高级选项", + "trapped": { + "title": "陷印状态", + "description": "指示文档是否已为高质量印刷做准备。", + "bullet1": "True:文档已进行陷印处理", + "bullet2": "False:文档未进行陷印处理", + "bullet3": "Unknown:未指定陷印状态" + } + } + } }, "fileToPDF": { "tags": "转换、格式、文档、图片、幻灯片、文本、转换、Office、Docs、Word、Excel、PowerPoint", @@ -883,6 +1431,7 @@ "ocr": { "tags": "识别、文本、图像、扫描、阅读、识别、检测、可编辑", "title": "OCR/扫描清理", + "desc": "清理和识别 PDF 中的图像文本,并将其转换为可编辑文本。", "header": "清理扫描件/OCR(光学字符识别)。", "selectText": { "1": "选择要在 PDF 中检测的语言(列出的语言是目前检测到的):", @@ -901,22 +1450,85 @@ "help": "请阅读此文档,了解如何将其用于其他语言和/或不在 docker 中使用。", "credit": "此服务使用 qpdf 和 Tesseract 进行 OCR。", "submit": "用 OCR 处理 PDF", - "desc": "清理和识别 PDF 中的图像文本,并将其转换为可编辑文本。", + "operation": { + "submit": "处理 OCR 并预览" + }, + "results": { + "title": "OCR 结果" + }, + "languagePicker": { + "additionalLanguages": "需要更多语言?", + "viewSetupGuide": "查看设置指南 →" + }, "settings": { "title": "设置", "ocrMode": { - "label": "OCR 模式" + "label": "OCR 模式", + "auto": "自动(跳过已有文本层)", + "force": "强制(重新识别全部并替换文本)", + "strict": "严格(发现文本即中止)" }, "languages": { - "label": "语言" + "label": "语言", + "placeholder": "选择语言" + }, + "compatibilityMode": { + "label": "兼容模式" + }, + "advancedOptions": { + "label": "处理选项", + "sidecar": "创建文本文件", + "deskew": "页面纠偏", + "clean": "清理输入文件", + "cleanFinal": "清理最终输出" } }, "tooltip": { + "header": { + "title": "OCR 设置概览" + }, "mode": { - "title": "OCR 模式" + "title": "OCR 模式", + "text": "光学字符识别(OCR)可将扫描或截图页面转换为可搜索、可复制或可高亮的文本。", + "bullet1": "自动:跳过已包含文本层的页面。", + "bullet2": "强制:重新识别每一页并替换所有文本。", + "bullet3": "严格:若发现可选择文本则终止。" }, "languages": { - "title": "语言" + "title": "语言", + "text": "指定预期语言可提升 OCR 准确度。可选择一种或多种语言以引导识别。" + }, + "output": { + "title": "输出", + "text": "决定文本输出的格式:", + "bullet1": "可搜索 PDF:在原图像背后嵌入文本。", + "bullet2": "HOCR XML:返回结构化机器可读文件。", + "bullet3": "纯文本边车:创建包含原始内容的独立 .txt 文件。" + }, + "advanced": { + "header": { + "title": "高级 OCR 处理" + }, + "compatibility": { + "title": "兼容模式", + "text": "使用 OCR “夹层 PDF” 模式:文件更大,但在某些语言和旧版软件上更可靠。默认使用 hOCR,适合更小、更现代的 PDF。" + }, + "sidecar": { + "title": "创建文本文件", + "text": "在 PDF 旁生成一个独立的 .txt 文件,包含所有提取的文本,便于访问与处理。" + }, + "deskew": { + "title": "页面纠偏", + "text": "自动校正倾斜页面以提升 OCR 准确度。适合未完美对齐的扫描文档。" + }, + "clean": { + "title": "清理输入文件", + "text": "在处理前通过去噪、增强对比度和优化图像来提升 OCR 识别效果。" + }, + "cleanFinal": { + "title": "清理最终输出", + "text": "在生成的 PDF 上进行后处理,移除 OCR 伪影并优化文本层以提高可读性并减小体积。" + } } } }, @@ -1028,27 +1640,107 @@ "header": "展平 PDF", "flattenOnlyForms": "仅展平表格", "submit": "展平", + "filenamePrefix": "flattened", + "files": { + "placeholder": "在主视图中选择一个 PDF 文件以开始" + }, "steps": { "settings": "设置" }, "options": { - "flattenOnlyForms": "仅展平表格" + "stepTitle": "扁平化选项", + "title": "扁平化选项", + "flattenOnlyForms.desc": "仅扁平化表单字段,保留其他交互元素", + "note": "扁平化会移除 PDF 的交互元素,使其不可编辑。" + }, + "results": { + "title": "扁平化结果" + }, + "error": { + "failed": "扁平化 PDF 时发生错误。" + }, + "tooltip": { + "header": { + "title": "关于 PDF 扁平化" + }, + "description": { + "title": "扁平化有什么作用?", + "text": "扁平化会将可填写的表单和按钮转换为普通文本和图像,使 PDF 不可编辑。外观保持不变,但不能再更改或填写表单。适合共享完成的表单、创建存档文件或确保各处显示一致。", + "bullet1": "文本框会变为普通文本(不可编辑)", + "bullet2": "复选框和按钮会变为图片", + "bullet3": "适用于不希望再更改的最终版本", + "bullet4": "确保在所有设备上外观一致" + }, + "formsOnly": { + "title": "“仅扁平化表单”是什么意思?", + "text": "此选项仅移除填写表单的能力,保留其它功能,如点击链接、查看书签与批注等。", + "bullet1": "表单将不可编辑", + "bullet2": "链接仍可点击", + "bullet3": "批注与备注仍可见", + "bullet4": "书签仍可用于导航" + } } }, "repair": { "tags": "修复、恢复、纠正、恢复", "title": "修复", "header": "修复 PDF", - "submit": "修复" + "submit": "修复", + "description": "该工具将尝试修复损坏或受损的 PDF 文件。无需额外设置。", + "filenamePrefix": "repaired", + "files": { + "placeholder": "在主视图中选择一个 PDF 文件以开始" + }, + "error": { + "failed": "修复 PDF 时发生错误。" + }, + "results": { + "title": "修复结果" + } }, "removeBlanks": { "tags": "清理、简化、非内容、整理", "title": "删除空白", "header": "删除空白页", - "threshold": "阈值:", - "thresholdDesc": "确定白色像素必须有多白的阈值", - "whitePercent": "白色百分比(%):", - "whitePercentDesc": "必须为白色才能删除的页面百分比", + "settings": { + "title": "设置" + }, + "threshold": { + "label": "像素白度阈值" + }, + "whitePercent": { + "label": "白色百分比阈值", + "unit": "%" + }, + "includeBlankPages": { + "label": "包含检测到的空白页" + }, + "tooltip": { + "header": { + "title": "移除空白页设置" + }, + "threshold": { + "title": "像素白度阈值", + "text": "控制像素被视为“白色”的标准。这有助于判断页面上哪些区域算作空白。", + "bullet1": "0 = 纯黑(最严格)", + "bullet2": "128 = 中灰", + "bullet3": "255 = 纯白(最宽松)" + }, + "whitePercent": { + "title": "白色百分比阈值", + "text": "设置页面被视为空白并移除所需的最小白色像素百分比。", + "bullet1": "较低值(如 80%)= 移除更多页面", + "bullet2": "较高值(如 95%)= 仅移除非常空白的页面", + "bullet3": "浅色背景文档建议使用较高值" + }, + "includeBlankPages": { + "title": "包含已检测空白页", + "text": "启用后,将创建一个单独的 PDF,包含从原文档中检测并移除的所有空白页。", + "bullet1": "有助于审查被移除的内容", + "bullet2": "有助于验证检测准确性", + "bullet3": "可关闭以减小输出文件大小" + } + }, "submit": "删除空白" }, "removeAnnotations": { @@ -1087,28 +1779,140 @@ "certSign": { "tags": "身份验证、PEM、P12、官方、加密", "title": "证书签名", - "header": "使用您的证书签名 PDF(进行中)", - "selectPDF": "选择要签名的 PDF 文件:", - "jksNote": "注意:如果您的证书类型未在下面列出,请使用keytool命令行工具将其转换为 Java Keystore(.jks)文件。 然后,选择下面的.jks文件选项。", - "selectKey": "选择您的私钥文件(PKCS#8格式,可以是.pem或.der):", - "selectCert": "选择您的证书文件(X.509格式,可以是.pem或.der):", - "selectP12": "选择您的 PKCS#12 密钥库文件(.p12或.pfx)(可选,如果提供,它应该包含您的私钥和证书):", - "selectJKS": "选择你的 Java Keystore 文件 (.jks或.keystore):", - "certType": "证书类型", - "password": "输入您的密钥库或私钥密码(如果有):", - "showSig": "显示签名", - "reason": "原因", - "location": "位置", - "name": "名称", - "showLogo": "显示 Logo", - "submit": "给 PDF 签名" + "filenamePrefix": "signed", + "signMode": { + "stepTitle": "签名模式", + "tooltip": { + "header": { + "title": "关于 PDF 签名" + }, + "overview": { + "title": "签名工作原理", + "text": "两种模式都会为文档加封(任何编辑都会被标记为篡改),并记录签署人/时间/方式以供审计。查看器的信任状态取决于证书链。" + }, + "manual": { + "title": "手动——自带证书", + "text": "使用您自己的证书文件以符合品牌身份。当 CA/链被识别时可显示受信任。", + "use": "适用:面向客户、法律、合规场景。" + }, + "auto": { + "title": "自动——零配置,系统即时封印", + "text": "使用服务器自签名证书签署。提供相同的防篡改封印审计追踪;在查看器中通常显示为未验证。", + "use": "适用场景:需要快速、且在内部评审与归档中保持一致身份。" + }, + "rule": { + "title": "经验法则", + "text": "需要收件人显示受信任?选手动。需要零配置、快速的防篡改封印与审计追踪?选自动。" + } + } + }, + "certTypeStep": { + "stepTitle": "证书格式" + }, + "certFiles": { + "stepTitle": "证书文件" + }, + "appearance": { + "stepTitle": "签名外观", + "tooltip": { + "header": { + "title": "关于签名外观" + }, + "invisible": { + "title": "不可见签名", + "text": "签名会添加到 PDF 中以提升安全性,但在查看文档时不可见。适合满足合规要求而不改变外观的场景。", + "bullet1": "在不改变视觉的情况下提供安全性", + "bullet2": "满足数字签名的法律要求", + "bullet3": "不影响文档版式或设计" + }, + "visible": { + "title": "可见签名", + "text": "在 PDF 上显示包含您的姓名、日期和可选信息的签名块。用于让读者清楚看到文档已签署。", + "bullet1": "在文档上显示签署人姓名与日期", + "bullet2": "可包含签署原因与地点", + "bullet3": "可选择放置签名的页面", + "bullet4": "可选添加徽标" + } + } + }, + "sign": { + "submit": "签署 PDF", + "results": "已签名 PDF" + }, + "error": { + "failed": "处理签名时发生错误。" + }, + "tooltip": { + "header": { + "title": "签名管理说明" + }, + "overview": { + "title": "该工具能做什么?", + "text": "此工具可检查您的 PDF 是否已数字签名,并可添加新的数字签名。数字签名可证明文档的创建者或批准者,并显示签署后是否被更改。", + "bullet1": "检查现有签名及其有效性", + "bullet2": "查看签署人和证书的详细信息", + "bullet3": "添加新的数字签名以保护文档", + "bullet4": "支持多个文件并便于导航" + }, + "validation": { + "title": "检查签名", + "text": "检查签名时,工具会告诉您签名是否有效、谁签了名、何时签的,以及自签署后文档是否被更改。", + "bullet1": "显示签名是否有效", + "bullet2": "显示签署人信息与签署日期", + "bullet3": "检查文档在签署后是否被修改", + "bullet4": "可使用自定义证书进行验证" + }, + "signing": { + "title": "添加签名", + "text": "要对 PDF 进行签名,您需要数字证书(如 PEM、PKCS12 或 JKS)。您可以选择让签名在文档上可见,或仅用于安全而不可见。", + "bullet1": "支持 PEM、PKCS12、JKS 以及服务器证书格式", + "bullet2": "可选择在 PDF 上显示或隐藏签名", + "bullet3": "可添加原因、地点和签署人姓名", + "bullet4": "可选择放置可见签名的页面", + "bullet5": "可使用服务器证书实现简便的“使用 Stirling-PDF 签名”选项" + } + }, + "certType": { + "tooltip": { + "header": { + "title": "关于证书类型" + }, + "what": { + "title": "什么是证书?", + "text": "它是您签名的安全身份标识,证明是您签署。除非要求使用证书签名,我们建议使用“键入、手写或上传”等其他安全方式。" + }, + "which": { + "title": "我该选哪种格式?", + "text": "选择与您的证书文件格式相匹配的选项:", + "bullet1": "PKCS#12(.p12 / .pfx)— 合并文件(最常见)", + "bullet2": "PFX(.pfx)— 微软的 PKCS12 版本", + "bullet3": "PEM — 独立的私钥与证书 .pem 文件", + "bullet4": "JKS — 面向开发/CI-CD 的 Java .jks 密钥库" + }, + "convert": { + "title": "未看到密钥?", + "text": "使用 keytool 将文件转换为 Java 密钥库(.jks),然后选择 JKS。" + } + } + } }, "removeCertSign": { "tags": "身份验证、PEM、P12、官方、加密", "title": "移除证书签名", "header": "移除 PDF 的证书签名", "selectPDF": "选择 PDF 文件:", - "submit": "移除签名" + "submit": "移除签名", + "description": "该工具将从您的 PDF 文档中移除数字证书签名。", + "filenamePrefix": "unsigned", + "files": { + "placeholder": "在主视图中选择一个 PDF 文件以开始" + }, + "error": { + "failed": "移除证书签名时发生错误。" + }, + "results": { + "title": "证书移除结果" + } }, "pageLayout": { "tags": "合并、组合、单视图、整理", @@ -1118,8 +1922,100 @@ "addBorder": "添加边框", "submit": "提交" }, + "bookletImposition": { + "tags": "booklet,imposition,printing,binding,folding,signature", + "title": "小册子拼版", + "header": "小册子拼版", + "submit": "创建小册子", + "spineLocation": { + "label": "书脊位置", + "left": "左侧(标准)", + "right": "右侧(RTL)" + }, + "doubleSided": { + "label": "双面打印", + "tooltip": "生成正反两面,以便正确的小册子打印" + }, + "manualDuplex": { + "title": "手动双面模式", + "instructions": "适用于无自动双面的打印机。您需要运行两次:" + }, + "duplexPass": { + "label": "打印走纸", + "first": "第 1 次走纸", + "second": "第 2 次走纸", + "firstInstructions": "先打印正面 → 叠放纸张(正面朝下)→ 进行第 2 次走纸", + "secondInstructions": "将已打印纸叠正面朝下放入 → 打印背面" + }, + "rtlBinding": { + "label": "从右向左装订", + "tooltip": "适用于阿拉伯语、希伯来语等从右到左的语言" + }, + "addBorder": { + "label": "在页面周围添加边框", + "tooltip": "在每个页面区块周围添加边框,便于裁切与对齐" + }, + "addGutter": { + "label": "添加装订边距", + "tooltip": "为装订添加内侧边距空间" + }, + "gutterSize": { + "label": "装订边距(点)" + }, + "flipOnShortEdge": { + "label": "短边翻转(仅自动双面)", + "tooltip": "用于短边翻转的自动双面打印(手动模式下忽略)", + "manualNote": "手动模式不需要——您将自行翻转纸堆" + }, + "advanced": { + "toggle": "高级选项" + }, + "paperSizeNote": "纸张大小将自动取自第一页。", + "tooltip": { + "header": { + "title": "小册子制作指南" + }, + "description": { + "title": "什么是小册子拼版?", + "text": "通过按正确的打印顺序排列页面来创建专业小册子。您的 PDF 页面将以横向纸张 2 合 1 的方式排布,折叠并装订后即可按顺序阅读。" + }, + "example": { + "title": "示例:8 页小册子", + "text": "您的 8 页文档将变为 2 张纸:", + "bullet1": "第 1 张正面:第 8、1 页 | 反面:第 2、7 页", + "bullet2": "第 2 张正面:第 6、3 页 | 反面:第 4、5 页", + "bullet3": "折叠并堆叠后:阅读顺序 1→2→3→4→5→6→7→8" + }, + "printing": { + "title": "打印与组装指南", + "text": "按照以下步骤获得完美小册子:", + "bullet1": "使用“长边翻转”的双面打印", + "bullet2": "按顺序堆叠纸张,对折", + "bullet3": "沿折痕装订或粘贴", + "bullet4": "短边翻转打印机:启用“短边翻转”选项" + }, + "manualDuplex": { + "title": "手动双面(单面打印机)", + "text": "适用于无自动双面的打印机:", + "bullet1": "关闭“双面打印”", + "bullet2": "选择“第 1 次走纸”→ 打印 → 纸堆正面朝下", + "bullet3": "选择“第 2 次走纸”→ 装入纸堆 → 打印背面", + "bullet4": "如常折叠与装订" + }, + "advanced": { + "title": "高级选项", + "text": "精细调整您的小册子:", + "bullet1": "RTL 装订:适用于阿拉伯语、希伯来语等", + "bullet2": "边框:显示用于修边的裁切线", + "bullet3": "装订边距:为装订/订书留出空间", + "bullet4": "短边翻转:仅适用于自动双面打印机" + } + }, + "error": { + "failed": "创建小册子拼版时发生错误。" + } + }, "scalePages": { - "tags": "调整大小、修改、尺寸、适应", "title": "调整页面缩放比例", "header": "调整页面缩放比例", "pageSize": "文档页面的尺寸。", @@ -1127,6 +2023,44 @@ "scaleFactor": "页面的缩放级别(裁剪)。", "submit": "提交" }, + "adjustPageScale": { + "tags": "resize,modify,dimension,adapt", + "title": "调整页面比例", + "header": "调整页面比例", + "scaleFactor": { + "label": "缩放系数" + }, + "pageSize": { + "label": "目标页面大小", + "keep": "保持原始大小", + "letter": "Letter(美式信纸)", + "legal": "Legal(美式政府纸)" + }, + "submit": "调整页面比例", + "error": { + "failed": "调整页面比例时发生错误。" + }, + "tooltip": { + "header": { + "title": "页面比例设置概览" + }, + "description": { + "title": "说明", + "text": "调整 PDF 内容尺寸并更改页面尺寸。" + }, + "scaleFactor": { + "title": "缩放系数", + "text": "控制页面上内容显示的大小。内容会按比例缩放并居中——若缩放后大于页面尺寸,可能会被裁切。", + "bullet1": "1.0 = 原始大小", + "bullet2": "0.5 = 一半大小(缩小 50%)", + "bullet3": "2.0 = 两倍大小(放大 200%,可能裁切)" + }, + "pageSize": { + "title": "目标页面大小", + "text": "设置输出 PDF 页面的尺寸。“保持原始大小”维持当前尺寸,其它选项会调整为标准纸张大小。" + } + } + }, "add-page-numbers": { "tags": "分页、标签、整理、索引" }, @@ -1134,7 +2068,29 @@ "tags": "自动检测、基于标题、整理、重新标记", "title": "自动重命名", "header": "自动重命名 PDF", - "submit": "自动重命名" + "description": "自动从 PDF 内容中查找标题,并将其用作文件名。", + "submit": "自动重命名", + "files": { + "placeholder": "在主视图中选择一个 PDF 文件以开始" + }, + "error": { + "failed": "自动重命名 PDF 时发生错误。" + }, + "results": { + "title": "自动重命名结果" + }, + "tooltip": { + "header": { + "title": "自动重命名原理" + }, + "howItWorks": { + "title": "智能重命名", + "text": "自动从 PDF 内容中查找标题,并将其用作文件名。", + "bullet1": "查找看起来像标题或标题级文本", + "bullet2": "根据检测到的标题创建干净、合法的文件名", + "bullet3": "若未找到合适标题,则保留原文件名" + } + } }, "adjust-contrast": { "tags": "颜色校正、调节、修改、增强" @@ -1143,7 +2099,36 @@ "tags": "修剪、缩小、编辑、形状", "title": "裁剪", "header": "裁剪 PDF", - "submit": "提交" + "submit": "提交", + "noFileSelected": "选择一个 PDF 文件开始裁剪", + "preview": { + "title": "裁剪区域选择" + }, + "reset": "重置为整页", + "coordinates": { + "title": "位置与尺寸", + "x": "X 位置", + "y": "Y 位置", + "width": "宽度", + "height": "高度" + }, + "error": { + "invalidArea": "裁剪区域超出 PDF 边界", + "failed": "裁剪 PDF 失败" + }, + "steps": { + "selectArea": "选择裁剪区域" + }, + "tooltip": { + "title": "如何裁剪 PDF", + "description": "通过拖拽并调整缩略图上的蓝色覆盖层选择要裁剪的区域。", + "drag": "拖动覆盖层以移动裁剪区域", + "resize": "拖动角与边的手柄来调整大小", + "precision": "使用坐标输入以获得精确位置" + }, + "results": { + "title": "裁剪结果" + } }, "autoSplitPDF": { "tags": "基于 QR 码、分离、扫描分割、整理", @@ -1226,64 +2211,122 @@ "downloadJS": "下载 JavaScript", "submit": "显示" }, - "autoRedact": { - "tags": "脱敏、隐藏、涂黑、标记、不可见", - "title": "自动删除", - "header": "自动删除", - "colorLabel": "颜色", - "textsToRedactLabel": "要删除的文本(每行一个)", - "textsToRedactPlaceholder": "例如:\\n保密\\n绝密", - "useRegexLabel": "使用正则表达式", - "wholeWordSearchLabel": "全字匹配", - "customPaddingLabel": "自定义额外间距", - "convertPDFToImageLabel": "将PDF转换为PDF-Image(用于删除方框后面的文本)", - "submitButton": "提交" - }, "redact": { "tags": "涂改,隐藏,涂黑,黑色,标记,遮蔽,手动", "title": "手动纠正", - "header": "手动纠正", "submit": "纠正", - "textBasedRedaction": "基于文本的纠正", - "pageBasedRedaction": "基于页面的纠正", - "convertPDFToImageLabel": "将PDF转换为PDF图像(用于删除框后的文本)", - "pageRedactionNumbers": { - "title": "页面", - "placeholder": "(例如 1,2,8 或 4,7,12-16 或 2n-1)" + "error": { + "failed": "涂黑 PDF 时发生错误。" }, - "redactionColor": { - "title": "编辑颜色" + "modeSelector": { + "title": "涂黑方式", + "mode": "模式", + "automatic": "自动", + "automaticDesc": "基于搜索词涂黑文本", + "manual": "手动", + "manualDesc": "点击并拖拽以涂黑特定区域", + "manualComingSoon": "手动涂黑即将推出" }, - "export": "导出", - "upload": "上传", - "boxRedaction": "框选区域涂黑", - "zoom": "缩放", - "zoomIn": "放大", - "zoomOut": "缩小", - "nextPage": "下一页", - "previousPage": "上一页", - "toggleSidebar": "切换侧边栏", - "showThumbnails": "显示缩略图", - "showDocumentOutline": "显示文档大纲(双击展开/折叠所有项目)", - "showAttatchments": "显示附件", - "showLayers": "显示图层(双击将所有图层重置为默认状态)", - "colourPicker": "颜色选择器", - "findCurrentOutlineItem": "查找当前大纲项目", - "applyChanges": "应用", "auto": { + "header": "自动涂黑", "settings": { + "title": "涂黑设置", "advancedTitle": "高级功能" }, + "colorLabel": "框颜色", "wordsToRedact": { - "add": "添加" + "title": "要涂黑的词语", + "placeholder": "输入一个词", + "add": "添加", + "examples": "示例:Confidential、Top-Secret" + }, + "useRegexLabel": "使用正则表达式", + "wholeWordSearchLabel": "整词匹配", + "customPaddingLabel": "自定义额外留白", + "convertPDFToImageLabel": "将 PDF 转为图像 PDF" + }, + "tooltip": { + "mode": { + "header": { + "title": "涂黑方式" + }, + "automatic": { + "title": "自动涂黑", + "text": "在整个文档中自动查找并涂黑指定文本。非常适合移除一致的敏感信息,如姓名、地址或机密标记。" + }, + "manual": { + "title": "手动涂黑", + "text": "点击并拖拽以手动选择特定区域进行涂黑。可精确控制需要涂黑的内容。(即将推出)" + } + }, + "words": { + "header": { + "title": "要涂黑的词语" + }, + "description": { + "title": "文本匹配", + "text": "输入要在文档中查找并涂黑的词语或短语。每个词会单独搜索。" + }, + "bullet1": "一次添加一个词", + "bullet2": "按 Enter 或点击“添加另一个”以添加", + "bullet3": "点击 × 以移除词语", + "examples": { + "title": "常见示例", + "text": "常见涂黑词包括:银行信息、电子邮箱地址或特定姓名。" + } + }, + "advanced": { + "header": { + "title": "高级涂黑设置" + }, + "color": { + "title": "框颜色与留白", + "text": "自定义涂黑框的外观。黑色为标准,但您可选择任意颜色。“留白”可在找到的文本周围添加额外空间。" + }, + "regex": { + "title": "使用正则", + "text": "启用正则表达式以进行高级模式匹配。适用于查找电话号码、邮箱或复杂模式。", + "bullet1": "示例:\\d{4}-\\d{2}-\\d{2} 匹配任意 YYYY-MM-DD 日期", + "bullet2": "谨慎使用——请充分测试" + }, + "wholeWord": { + "title": "整词匹配", + "text": "仅匹配完整单词,而非部分匹配。启用后,“John”不会匹配“Johnson”。" + }, + "convert": { + "title": "转为图像 PDF", + "text": "在涂黑后将 PDF 转换为基于图像的 PDF。确保框下文字被完全移除且不可恢复。" + } } }, "manual": { + "header": "手动涂黑", + "textBasedRedaction": "基于文本的涂黑", + "pageBasedRedaction": "基于页面的涂黑", + "convertPDFToImageLabel": "将 PDF 转为图像 PDF(用于移除框后面的文字)", "pageRedactionNumbers": { "title": "页码", "placeholder": "(例如:1,2,8 或 4,7,12-16 或 2n-1)" }, - "export": "导出全部" + "redactionColor": { + "title": "涂黑颜色" + }, + "export": "导出全部", + "upload": "上传", + "boxRedaction": "框选涂黑", + "zoom": "缩放", + "zoomIn": "放大", + "zoomOut": "缩小", + "nextPage": "下一页", + "previousPage": "上一页", + "toggleSidebar": "切换侧边栏", + "showThumbnails": "显示缩略图", + "showDocumentOutline": "显示文档大纲(双击可展开/折叠所有项目)", + "showAttachments": "显示附件", + "showLayers": "显示图层(双击可重置所有图层到默认状态)", + "colourPicker": "取色器", + "findCurrentOutlineItem": "定位当前大纲项", + "applyChanges": "应用更改" } }, "tableExtraxt": { @@ -1337,6 +2380,7 @@ "tags": "图章、添加图片、图片居中、水印、PDF、嵌入、自定义", "header": "添加图章", "title": "添加图章", + "stampSetup": "印章设置", "stampType": "图章类型", "stampText": "图章文字", "stampImage": "图章图片", @@ -1349,7 +2393,8 @@ "overrideY": "覆盖Y坐标", "customMargin": "自定义外边距", "customColor": "自定义文本颜色", - "submit": "提交" + "submit": "提交", + "noStampSelected": "未选择印章。返回到第 1 步。" }, "removeImagePdf": { "tags": "删除图像, 页面操作, 后端, 服务端" @@ -1428,6 +2473,8 @@ "title": "登录", "header": "登录", "signin": "登录", + "signInWith": "使用以下方式登录", + "signInAnonymously": "以访客身份注册", "rememberme": "记住我", "invalid": "用户名或密码无效。", "locked": "您的账户已被锁定。", @@ -1446,12 +2493,70 @@ "alreadyLoggedIn": "您已经登录到了", "alreadyLoggedIn2": "设备,请注销设备后重试。", "toManySessions": "你已经有太多的会话了。请注销一些设备后重试。", - "logoutMessage": "您已退出登录。" + "logoutMessage": "您已退出登录。", + "youAreLoggedIn": "您已登录!", + "email": "邮箱", + "password": "密码", + "enterEmail": "输入您的邮箱", + "enterPassword": "输入您的密码", + "loggingIn": "正在登录…", + "signingIn": "正在登录…", + "login": "登录", + "or": "或", + "useMagicLink": "改用魔法链接", + "enterEmailForMagicLink": "输入您的邮箱以获取魔法链接", + "sending": "正在发送…", + "sendMagicLink": "发送魔法链接", + "cancel": "取消", + "dontHaveAccount": "还没有账户?去注册", + "home": "首页", + "debug": "调试", + "signOut": "退出登录", + "pleaseEnterBoth": "请同时输入邮箱和密码", + "pleaseEnterEmail": "请输入您的邮箱地址", + "magicLinkSent": "魔法链接已发送至 {{email}}!请检查邮箱并点击链接登录。", + "passwordResetSent": "密码重置链接已发送至 {{email}}!请检查邮箱并按指引操作。", + "failedToSignIn": "使用 {{provider}} 登录失败:{{message}}", + "unexpectedError": "意外错误:{{message}}" + }, + "signup": { + "title": "创建账户", + "subtitle": "加入 Stirling PDF 开始使用", + "name": "姓名", + "email": "邮箱", + "password": "密码", + "confirmPassword": "确认密码", + "enterName": "输入您的姓名", + "enterEmail": "输入您的邮箱", + "enterPassword": "输入您的密码", + "confirmPasswordPlaceholder": "确认密码", + "or": "或", + "creatingAccount": "正在创建账户…", + "signUp": "注册", + "alreadyHaveAccount": "已经有账户?去登录", + "pleaseFillAllFields": "请填写所有字段", + "passwordsDoNotMatch": "两次输入的密码不一致", + "passwordTooShort": "密码至少为 6 个字符", + "invalidEmail": "请输入有效的邮箱地址", + "checkEmailConfirmation": "请检查邮箱中的确认链接以完成注册。", + "accountCreatedSuccessfully": "账户创建成功!您现在可以登录。", + "unexpectedError": "意外错误:{{message}}" }, "pdfToSinglePage": { "title": "PDF 转单页", "header": "将 PDF 转换为单页", - "submit": "转为单页" + "submit": "转为单页", + "description": "该工具会将 PDF 的所有页面合并为一张超长单页。宽度保持与原页面相同,高度为所有页面高度之和。", + "filenamePrefix": "single_page", + "files": { + "placeholder": "在主视图中选择一个 PDF 文件以开始" + }, + "error": { + "failed": "转换为单页时发生错误。" + }, + "results": { + "title": "单页结果" + } }, "pageExtracter": { "title": "提取页面", @@ -1482,24 +2587,49 @@ }, "compress": { "title": "压缩", + "desc": "压缩 PDF 以减小文件大小。", "header": "压缩 PDF", + "method": { + "title": "压缩方式", + "quality": "质量", + "filesize": "文件大小" + }, "credit": "此服务使用qpdf进行 PDF 压缩/优化。", "grayscale": { "label": "应用灰度进行压缩" }, + "tooltip": { + "header": { + "title": "压缩设置概览" + }, + "description": { + "title": "说明", + "text": "压缩是减小文件大小的简便方法。选择“文件大小”可输入目标大小,我们将为您自动调整质量;选择“质量”可手动设置压缩强度。" + }, + "qualityAdjustment": { + "title": "质量调整", + "text": "拖动滑块以调整压缩强度。较低值(1-3)可保留质量但文件较大;较高值(7-9)能缩小体积但会降低图像清晰度。", + "bullet1": "较低数值更能保留质量", + "bullet2": "较高数值能更大幅度减小文件" + }, + "grayscale": { + "title": "灰度", + "text": "选择此选项将所有图像转换为黑白,这对扫描 PDF 或图片较多的文档可显著减小体积。" + } + }, + "error": { + "failed": "压缩 PDF 时发生错误。" + }, "selectText": { "1": { - "_value": "Compression Settings", + "_value": "压缩设置", "1": "1-3 PDF 压缩,
4-6 轻度图像压缩,
7-9 深度图像压缩(将显著降低图像质量)" }, "2": "优化级别:", "4": "自动模式 - 自动调整质量以获得精确大小的PDF", "5": "预期PDF大小(例如:25MB、10.8MB、25KB)" }, - "submit": "压缩", - "method": { - "filesize": "文件大小" - } + "submit": "压缩" }, "decrypt": { "passwordPrompt": "此文件受密码保护。请输入密码:", @@ -1634,6 +2764,12 @@ }, "note": "版本说明仅提供英文版本" }, + "swagger": { + "title": "API 文档", + "header": "API 文档", + "desc": "查看并测试 Stirling PDF 的 API 端点", + "tags": "api,documentation,swagger,endpoints,development" + }, "cookieBanner": { "popUp": { "title": "我们如何使用 Cookie", @@ -1671,52 +2807,289 @@ } } }, - "download": "下载", - "undo": "撤销", - "convert": { - "title": "转换", - "settings": "设置", - "color": "颜色", - "greyscale": "灰度", - "fillPage": "填充页面", - "pdfaDigitalSignatureWarning": "该PDF包含数字签名,下一步将移除该签名。", - "grayscale": "灰度" + "removeMetadata": { + "submit": "移除元数据" }, - "attachments": { - "tags": "嵌入、附件、文件、附加", - "title": "添加附件", - "header": "添加附件", - "submit": "添加附件" + "sidebar": { + "toggle": "切换侧栏" + }, + "theme": { + "toggle": "切换主题" + }, + "view": { + "viewer": "查看器", + "pageEditor": "页面编辑器", + "fileManager": "文件管理器" + }, + "pageEditor": { + "title": "页面编辑器", + "save": "保存更改", + "noPdfLoaded": "未加载 PDF。请先上传以进行编辑。", + "rotatedLeft": "已向左旋转:", + "rotatedRight": "已向右旋转:", + "deleted": "已删除:", + "movedLeft": "向左移动:", + "movedRight": "向右移动:", + "splitAt": "拆分于:", + "insertedPageBreak": "已插入分页位置:", + "addFileNotImplemented": "演示中未实现“添加文件”", + "closePdf": "关闭 PDF", + "reset": "重置更改", + "zoomIn": "放大", + "zoomOut": "缩小", + "fitToWidth": "适配宽度", + "actualSize": "实际大小" + }, + "viewer": { + "firstPage": "第一页", + "lastPage": "最后一页", + "previousPage": "上一页", + "nextPage": "下一页", + "zoomIn": "放大", + "zoomOut": "缩小", + "singlePageView": "单页视图", + "dualPageView": "双页视图" }, "rightRail": { + "closeSelected": "关闭所选文件", "selectAll": "选择所有", - "deselectAll": "取消选择所有" + "deselectAll": "取消选择所有", + "selectByNumber": "按页码选择", + "deleteSelected": "删除所选页面", + "closePdf": "关闭 PDF", + "exportAll": "导出 PDF", + "downloadSelected": "下载所选文件", + "downloadAll": "全部下载", + "toggleTheme": "切换主题", + "language": "语言", + "search": "搜索 PDF", + "panMode": "平移模式", + "rotateLeft": "向左旋转", + "rotateRight": "向右旋转", + "toggleSidebar": "切换侧边栏" + }, + "search": { + "title": "搜索 PDF", + "placeholder": "输入搜索词…" + }, + "guestBanner": { + "title": "您正在以访客身份使用 Stirling PDF!", + "message": "创建免费账户以保存您的工作、获取更多功能并支持项目。", + "dismiss": "关闭横幅", + "signUp": "免费注册" + }, + "toolPicker": { + "searchPlaceholder": "搜索工具…", + "noToolsFound": "未找到工具", + "allTools": "所有工具", + "quickAccess": "快速访问", + "categories": { + "standardTools": "标准工具", + "advancedTools": "高级工具", + "recommendedTools": "推荐工具" + }, + "subcategories": { + "signing": "签署", + "documentSecurity": "文档安全", + "verification": "验证", + "documentReview": "文档审阅", + "pageFormatting": "页面格式", + "extraction": "提取", + "removal": "移除", + "automation": "自动化", + "general": "通用", + "advancedFormatting": "高级排版", + "developerTools": "开发者工具" + } }, "quickAccess": { - "sign": "签名" + "read": "阅读", + "sign": "签名", + "automate": "自动化", + "files": "文件", + "activity": "活动", + "config": "配置", + "allTools": "全部工具" }, "fileUpload": { + "selectFile": "选择文件", + "selectFiles": "选择文件", + "selectPdfToView": "选择一个 PDF 进行查看", + "selectPdfToEdit": "选择一个 PDF 进行编辑", + "chooseFromStorage": "从存储中选择或上传新的 PDF", + "chooseFromStorageMultiple": "从存储中选择文件或上传新的 PDF", + "loadFromStorage": "从存储加载", + "filesAvailable": "个可用文件", "loading": "加载中...", - "or": "或" + "or": "或", + "dropFileHere": "将文件拖到此处或点击上传", + "dropFilesHere": "将文件拖到此处或点击上传按钮", + "pdfFilesOnly": "仅限 PDF 文件", + "supportedFileTypes": "支持的文件类型", + "upload": "上传", + "uploadFile": "上传文件", + "uploadFiles": "上传文件", + "noFilesInStorage": "存储中没有可用文件。请先上传。", + "selectFromStorage": "从存储中选择", + "backToTools": "返回工具", + "addFiles": "添加文件", + "dragFilesInOrClick": "拖入文件或点击“添加文件”浏览" }, "fileManager": { + "title": "上传 PDF 文件", + "subtitle": "将文件添加到您的存储,以便在各工具之间轻松访问", + "filesSelected": "个文件已选", + "clearSelection": "清除所选", + "openInFileEditor": "在文件编辑器中打开", + "uploadError": "部分文件上传失败。", + "failedToOpen": "无法打开文件。可能已从存储中移除。", + "failedToLoad": "无法将文件加载到活动集合。", + "storageCleared": "浏览器已清空存储。文件已被移除。请重新上传。", + "clearAll": "全部清除", + "reloadFiles": "重新加载文件", + "dragDrop": "拖放文件到此处", + "clickToUpload": "点击以上传文件", + "selectedFiles": "已选文件", + "storage": "存储", + "filesStored": "个文件已存储", + "storageError": "发生存储错误", + "storageLow": "存储空间不足。请考虑移除旧文件。", + "supportMessage": "由浏览器数据库存储提供支持,容量无限", + "noFileSelected": "未选择任何文件", + "showHistory": "显示历史", + "hideHistory": "隐藏历史", + "fileHistory": "文件历史", + "loadingHistory": "正在加载历史…", + "lastModified": "上次修改", + "toolChain": "已应用工具", + "restore": "恢复", + "searchFiles": "搜索文件…", + "recent": "最近", + "localFiles": "本地文件", + "googleDrive": "Google 云端硬盘", + "googleDriveShort": "Drive", + "myFiles": "我的文件", + "noRecentFiles": "未找到最近文件", + "dropFilesHint": "将文件拖到此处即可上传", + "googleDriveNotAvailable": "不可使用 Google 云端硬盘集成", + "openFiles": "打开文件", + "openFile": "打开文件", + "details": "文件详情", "fileName": "名称", + "fileFormat": "格式", + "fileSize": "大小", "fileVersion": "版本", + "totalSelected": "合计已选", + "dropFilesHere": "将文件拖放到此处", "selectAll": "选择所有", "deselectAll": "取消选择所有", "deleteSelected": "删除已选", + "downloadSelected": "下载所选", + "selectedCount": "已选 {{count}}", "download": "下载", - "delete": "删除" + "delete": "删除", + "unsupported": "不支持" + }, + "storage": { + "temporaryNotice": "文件临时存储在您的浏览器中,可能会被自动清除", + "storageLimit": "存储限制", + "storageUsed": "已用临时存储", + "storageFull": "存储空间几乎已满。请考虑移除部分文件。", + "fileTooLarge": "文件过大。单个文件的最大大小为", + "storageQuotaExceeded": "超出存储配额。请移除部分文件后再上传。", + "approximateSize": "大约大小" }, "sanitize": { + "title": "安全清理", + "desc": "移除 PDF 文件中的潜在有害元素。", "submit": "清理 PDF", + "completed": "安全清理成功完成", + "error.generic": "安全清理失败", + "error.failed": "安全清理 PDF 时发生错误。", + "filenamePrefix": "sanitised", + "sanitizationResults": "安全清理结果", "steps": { - "settings": "设置" + "files": "文件", + "settings": "设置", + "results": "结果" + }, + "files": { + "placeholder": "在主视图中选择一个 PDF 文件以开始" + }, + "options": { + "title": "安全清理选项", + "note": "请选择要从 PDF 中移除的元素。至少需要选择一个选项。", + "removeJavaScript.desc": "移除 PDF 中的 JavaScript 操作与脚本", + "removeEmbeddedFiles.desc": "移除嵌入在 PDF 中的任何文件", + "removeXMPMetadata.desc": "从 PDF 中移除 XMP 元数据", + "removeMetadata.desc": "移除文档信息元数据(标题、作者等)", + "removeLinks.desc": "移除外部链接与启动动作", + "removeFonts.desc": "从 PDF 中移除嵌入字体" + } + }, + "addPassword": { + "title": "添加密码", + "desc": "使用密码加密您的 PDF 文档。", + "completed": "已应用密码保护", + "submit": "加密", + "filenamePrefix": "encrypted", + "error": { + "failed": "加密 PDF 时发生错误。" + }, + "passwords": { + "stepTitle": "密码与加密", + "completed": "密码已配置", + "user": { + "label": "用户密码", + "placeholder": "输入用户密码" + }, + "owner": { + "label": "所有者密码", + "placeholder": "输入所有者密码" + } + }, + "encryption": { + "keyLength": { + "label": "加密密钥长度", + "40bit": "40 位(低)", + "128bit": "128 位(标准)", + "256bit": "256 位(高)" + } + }, + "results": { + "title": "已加密的 PDF" + }, + "tooltip": { + "header": { + "title": "密码保护概览" + }, + "passwords": { + "title": "密码类型", + "text": "用户密码限制打开文档;所有者密码控制打开后可执行的操作。您可以同时设置或仅设置其中之一。", + "bullet1": "用户密码:打开 PDF 时需要", + "bullet2": "所有者密码:控制文档权限(并非所有查看器都支持)" + }, + "encryption": { + "title": "加密级别", + "text": "更高的加密级别提供更好的安全性,但可能不被较旧的 PDF 查看器支持。", + "bullet1": "40 位:基础安全性,与旧版查看器兼容", + "bullet2": "128 位:标准安全性,广泛支持", + "bullet3": "256 位:最高安全性,需要现代查看器" + }, + "permissions": { + "title": "更改权限", + "text": "这些权限控制用户对 PDF 的操作。与所有者密码配合时效果最佳。" + } } }, "changePermissions": { "title": "更改权限", + "desc": "更改文档限制与权限。", + "completed": "权限已更改", "submit": "更改权限", + "error": { + "failed": "更改 PDF 权限时发生错误。" + }, "permissions": { "preventAssembly": { "label": "防止文件的拼接" @@ -1743,10 +3116,183 @@ "label": "防止打印不同的格式" } }, + "results": { + "title": "已修改的 PDF" + }, "tooltip": { "header": { "title": "更改权限" + }, + "description": { + "text": "更改文档权限,允许/禁止在 PDF 阅读器中的不同功能。" + }, + "warning": { + "text": "要使这些权限不可更改,请使用“添加密码”工具设置所有者密码。" } } - } + }, + "removePassword": { + "title": "删除密码", + "desc": "从 PDF 文档中移除密码保护。", + "tags": "安全、解密、密码、安全性、删除密码", + "password": { + "stepTitle": "删除密码", + "label": "当前密码", + "placeholder": "输入当前密码", + "completed": "密码已配置" + }, + "filenamePrefix": "decrypted", + "error": { + "failed": "移除 PDF 密码时发生错误。" + }, + "tooltip": { + "description": "移除密码保护需要用于加密 PDF 的原始密码。这样会解密文档,使其无需密码即可访问。" + }, + "submit": "删除", + "results": { + "title": "已解密的 PDF" + } + }, + "automate": { + "title": "自动化", + "desc": "通过串联 PDF 操作构建多步工作流。适合重复性任务。", + "invalidStep": "无效步骤", + "files": { + "placeholder": "选择要用此自动化处理的文件" + }, + "selection": { + "title": "自动化选择", + "saved": { + "title": "已保存" + }, + "createNew": { + "title": "新建自动化" + }, + "suggested": { + "title": "推荐" + } + }, + "creation": { + "createTitle": "创建自动化", + "editTitle": "编辑自动化", + "intro": "自动化会按顺序运行工具。开始前,请按希望的顺序添加工具。", + "name": { + "label": "自动化名称", + "placeholder": "我的自动化" + }, + "description": { + "label": "描述(可选)", + "placeholder": "描述此自动化的作用…" + }, + "tools": { + "selectTool": "选择工具…", + "selected": "已选工具", + "remove": "移除工具", + "configure": "配置工具", + "notConfigured": "!未配置", + "addTool": "添加工具", + "add": "添加工具…" + }, + "save": "保存自动化", + "unsavedChanges": { + "title": "未保存的更改", + "message": "您有未保存的更改。确定要返回吗?所有更改都将丢失。", + "cancel": "取消", + "confirm": "返回" + }, + "icon": { + "label": "图标" + } + }, + "run": { + "title": "运行自动化" + }, + "sequence": { + "unnamed": "未命名自动化", + "steps": "{{count}} 个步骤", + "running": "正在运行自动化…", + "run": "运行自动化", + "finish": "完成" + }, + "reviewTitle": "自动化结果", + "config": { + "loading": "正在加载工具配置…", + "noSettings": "此工具没有可配置的设置。", + "title": "配置 {{toolName}}", + "description": "配置此工具的设置。这些设置将在自动化运行时应用。", + "cancel": "取消", + "save": "保存配置" + }, + "copyToSaved": "复制到“已保存”" + }, + "automation": { + "suggested": { + "securePdfIngestion": "安全 PDF 采集", + "securePdfIngestionDesc": "全面的 PDF 处理流程:清理潜在风险内容、执行 OCR(含清理)、转换为 PDF/A 以便长期存档,并优化文件大小。", + "emailPreparation": "邮件准备", + "emailPreparationDesc": "通过压缩文件、将大型文档拆分为适合邮件的 20MB 块,并移除元数据来优化用于邮件分发的 PDF。", + "secureWorkflow": "安全工作流", + "secureWorkflowDesc": "通过移除可能的恶意内容(如 JavaScript 和嵌入文件),再添加密码保护来保护 PDF。默认密码为“password”。", + "processImages": "处理图像", + "processImagesDesc": "将多个图像文件合并为一个 PDF,然后应用 OCR 技术从图像中提取可搜索文本。" + } + }, + "common": { + "copy": "复制", + "copied": "已复制!", + "refresh": "刷新", + "retry": "重试", + "remaining": "剩余", + "used": "已用", + "available": "可用", + "cancel": "取消" + }, + "config": { + "account": { + "overview": { + "title": "账户设置", + "manageAccountPreferences": "管理您的账户偏好", + "guestDescription": "您以访客身份登录。可考虑在上方升级您的账户。" + }, + "upgrade": { + "title": "升级访客账户", + "description": "关联您的账户以保留历史并获得更多功能!", + "socialLogin": "使用社交账号升级", + "linkWith": "关联方式", + "emailPassword": "或输入您的邮箱与密码", + "email": "邮箱", + "emailPlaceholder": "输入您的邮箱", + "password": "密码(可选)", + "passwordPlaceholder": "设置密码", + "passwordNote": "留空则仅使用邮箱验证", + "upgradeButton": "升级账户" + } + }, + "apiKeys": { + "description": "用于访问 Stirling 一系列 PDF 工具的 API 密钥。可复制到您的项目,或刷新以生成新密钥。", + "publicKeyAriaLabel": "公共 API 密钥", + "copyKeyAriaLabel": "复制 API 密钥", + "refreshAriaLabel": "刷新 API 密钥", + "includedCredits": "包含积分", + "purchasedCredits": "已购积分", + "totalCredits": "总积分", + "chartAriaLabel": "积分使用:已用包含 {{includedUsed}} / {{includedTotal}},已用购买 {{purchasedUsed}} / {{purchasedTotal}}", + "nextReset": "下次重置", + "lastApiUse": "上次 API 使用", + "overlayMessage": "生成密钥后可查看积分与可用额度", + "label": "API 密钥", + "guestInfo": "访客用户不提供 API 密钥。创建账户以获取可在应用中使用的密钥。", + "goToAccount": "前往账户", + "refreshModal": { + "title": "刷新 API 密钥", + "warning": "⚠️ 警告:此操作将生成新的 API 密钥并使之前的密钥失效。", + "impact": "任何当前使用这些密钥的应用或服务将停止工作,直至您更新为新密钥。", + "confirmPrompt": "确定要继续吗?", + "confirmCta": "刷新密钥" + }, + "generateError": "我们无法生成您的 API 密钥。" + } + }, + "termsAndConditions": "条款与条件", + "logOut": "退出登录" } \ No newline at end of file diff --git a/scripts/translations/README.md b/scripts/translations/README.md new file mode 100644 index 000000000..2688e8537 --- /dev/null +++ b/scripts/translations/README.md @@ -0,0 +1,403 @@ +# Translation Management Scripts + +This directory contains Python scripts for managing frontend translations in Stirling PDF. These tools help analyze, merge, and manage translations against the en-GB golden truth file. + +## Scripts Overview + +### 1. `translation_analyzer.py` +Analyzes translation files to find missing translations, untranslated entries, and provides completion statistics. + +**Usage:** +```bash +# Analyze all languages +python scripts/translations/translation_analyzer.py + +# Analyze specific language +python scripts/translations/translation_analyzer.py --language fr-FR + +# Show only missing translations +python scripts/translations/translation_analyzer.py --missing-only + +# Show only untranslated entries +python scripts/translations/translation_analyzer.py --untranslated-only + +# Show summary only +python scripts/translations/translation_analyzer.py --summary + +# JSON output format +python scripts/translations/translation_analyzer.py --format json +``` + +**Features:** +- Finds missing translation keys +- Identifies untranslated entries (identical to en-GB and [UNTRANSLATED] markers) +- Shows accurate completion percentages using ignore patterns +- Identifies extra keys not in en-GB +- Supports JSON and text output formats +- Uses `scripts/ignore_translation.toml` for language-specific exclusions + +### 2. `translation_merger.py` +Merges missing translations from en-GB into target language files and manages translation workflows. + +**Usage:** +```bash +# Add missing translations from en-GB to French +python scripts/translations/translation_merger.py fr-FR add-missing + +# Add without marking as [UNTRANSLATED] +python scripts/translations/translation_merger.py fr-FR add-missing --no-mark-untranslated + +# Extract untranslated entries to a file +python scripts/translations/translation_merger.py fr-FR extract-untranslated --output fr_untranslated.json + +# Create a template for AI translation +python scripts/translations/translation_merger.py fr-FR create-template --output fr_template.json + +# Apply translations from a file +python scripts/translations/translation_merger.py fr-FR apply-translations --translations-file fr_translated.json +``` + +**Features:** +- Adds missing keys from en-GB with optional [UNTRANSLATED] markers +- Extracts untranslated entries for external translation +- Creates structured templates for AI translation +- Applies translated content back to language files +- Automatic backup creation + +### 3. `ai_translation_helper.py` +Specialized tool for AI-assisted translation workflows with batch processing and validation. + +**Usage:** +```bash +# Create batch file for AI translation (multiple languages) +python scripts/translations/ai_translation_helper.py create-batch --languages fr-FR de-DE es-ES --output batch.json --max-entries 50 + +# Validate AI translations +python scripts/translations/ai_translation_helper.py validate batch.json + +# Apply validated AI translations +python scripts/translations/ai_translation_helper.py apply-batch batch.json + +# Export for external translation services +python scripts/translations/ai_translation_helper.py export --languages fr-FR de-DE --format csv +``` + +**Features:** +- Creates batch files for AI translation of multiple languages +- Prioritizes important translation keys +- Validates translations for placeholders and artifacts +- Applies batch translations with validation +- Exports to CSV/JSON for external translation services + +### 4. `compact_translator.py` +Extracts untranslated entries in minimal JSON format for character-limited AI services. + +**Usage:** +```bash +# Extract all untranslated entries +python scripts/translations/compact_translator.py it-IT --output to_translate.json +``` + +**Features:** +- Produces minimal JSON output with no extra whitespace +- Automatic ignore patterns for cleaner output +- Batch size control for manageable chunks +- 50-80% fewer characters than other extraction methods + +### 5. `json_beautifier.py` +Restructures and beautifies translation JSON files to match en-GB structure exactly. + +**Usage:** +```bash +# Restructure single language to match en-GB structure +python scripts/translations/json_beautifier.py --language de-DE + +# Restructure all languages +python scripts/translations/json_beautifier.py --all-languages + +# Validate structure without modifying files +python scripts/translations/json_beautifier.py --language de-DE --validate-only + +# Skip backup creation +python scripts/translations/json_beautifier.py --language de-DE --no-backup +``` + +**Features:** +- Restructures JSON to match en-GB nested structure exactly +- Preserves key ordering for line-by-line comparison +- Creates automatic backups before modification +- Validates structure and key ordering +- Handles flattened dot-notation keys (e.g., "key.subkey") properly + +## Translation Workflows + +### Method 1: Compact Translation Workflow (RECOMMENDED for AI) + +**Best for character-limited AI services like Claude or ChatGPT** + +#### Step 1: Check Current Status +```bash +python scripts/translations/translation_analyzer.py --language it-IT --summary +``` + +#### Step 2: Extract Untranslated Entries +```bash +python scripts/translations/compact_translator.py it-IT --output to_translate.json +``` + +**Output format**: Compact JSON with minimal whitespace +```json +{"key1":"English text","key2":"Another text","key3":"More text"} +``` + +#### Step 3: AI Translation +1. Copy the compact JSON output +2. Give it to your AI with instructions: + ``` + Translate this JSON to Italian. Keep the same structure, translate only the values. + Preserve placeholders like {n}, {total}, {filename}, {{variable}}. + ``` +3. Save the AI's response as `translated.json` + +#### Step 4: Apply Translations +```bash +python scripts/translations/translation_merger.py it-IT apply-translations --translations-file translated.json +``` + +#### Step 5: Verify Results +```bash +python scripts/translations/translation_analyzer.py --language it-IT --summary +``` + +### Method 2: Batch Translation Workflow + +**For complete language translation from scratch or major updates** + +#### Step 1: Analyze Current State +```bash +python scripts/translations/translation_analyzer.py --language de-DE --summary +``` + +#### Step 2: Create Translation Batches +```bash +# Create batches of 100 entries each for systematic translation +python scripts/translations/ai_translation_helper.py create-batch --languages de-DE --output de_batch_1.json --max-entries 100 +``` + +#### Step 3: Translate Batch with AI +Edit the batch file and fill in ALL `translated` fields: +- Preserve all placeholders like `{n}`, `{total}`, `{filename}`, `{{toolName}}` +- Keep technical terms consistent +- Maintain JSON structure exactly +- Consider context provided for each entry + +#### Step 4: Apply Translations +```bash +# Skip validation if using legitimate placeholders ({{variable}}) +python scripts/translations/ai_translation_helper.py apply-batch de_batch_1.json --skip-validation +``` + +#### Step 5: Check Progress and Continue +```bash +python scripts/translations/translation_analyzer.py --language de-DE --summary +``` +Repeat steps 2-5 until 100% complete. + +### Method 3: Quick Translation Workflow (Legacy) + +**For small updates or existing translations** + +#### Step 1: Add Missing Translations +```bash +python scripts/translations/translation_merger.py fr-FR add-missing --mark-untranslated +``` + +#### Step 2: Create AI Template +```bash +python scripts/translations/translation_merger.py fr-FR create-template --output fr_template.json +``` + +#### Step 3: Apply Translations +```bash +python scripts/translations/translation_merger.py fr-FR apply-translations --translations-file fr_translated.json +``` + +## Translation File Structure + +Translation files are located in `frontend/public/locales/{language}/translation.json` with nested JSON structure: + +```json +{ + "addPageNumbers": { + "title": "Add Page Numbers", + "selectText": { + "1": "Select PDF file:", + "2": "Margin Size" + } + } +} +``` + +Keys use dot notation internally (e.g., `addPageNumbers.selectText.1`). + +## Key Features + +### Placeholder Preservation +All scripts preserve placeholders like `{n}`, `{total}`, `{filename}` in translations: +``` +"customNumberDesc": "Defaults to {n}, also accepts 'Page {n} of {total}'" +``` + +### Automatic Backups +Scripts create timestamped backups before modifying files: +``` +translation.backup.20241201_143022.json +``` + +### Context-Aware Translation +Scripts provide context information to help with accurate translations: +```json +{ + "addPageNumbers.title": { + "original": "Add Page Numbers", + "context": "Feature for adding page numbers to PDFs" + } +} +``` + +### Priority-Based Translation +Important keys (title, submit, error messages) are prioritized when limiting translation batch sizes. + +### Ignore Patterns System +The `scripts/ignore_translation.toml` file defines keys that should be ignored for each language, improving completion accuracy. + +**Common ignore patterns:** +- `language.direction`: Text direction (ltr/rtl) - universal +- `lang.*`: Language code entries not relevant to specific locales +- `pipeline.title`, `home.devApi.title`: Technical terms kept in English +- Specific technical IDs, version numbers, and system identifiers + +**Format:** +```toml +[de_DE] +ignore = [ + 'language.direction', + 'pipeline.title', + 'lang.afr', + 'lang.ceb', + # ... more patterns +] +``` + +## Best Practices & Lessons Learned + +### Critical Rules for Translation + +1. **NEVER skip entries**: Translate ALL entries in each batch to avoid [UNTRANSLATED] pollution +2. **Use appropriate batch sizes**: 100 entries for systematic translation, unlimited for compact method +3. **Skip validation for placeholders**: Use `--skip-validation` when batch contains `{{variable}}` patterns +4. **Check progress between batches**: Use `--summary` flag to track completion percentage +5. **Preserve all placeholders**: Keep `{n}`, `{total}`, `{filename}`, `{{toolName}}` exactly as-is + +### Workflow Comparison + +| Method | Best For | Character Usage | Complexity | Speed | +|--------|----------|----------------|------------|-------| +| Compact | AI services | Minimal (50-80% less) | Simple | Fastest | +| Batch | Systematic translation | Moderate | Medium | Medium | +| Quick | Small updates | High | Low | Slow | + +### Common Issues and Solutions + +#### [UNTRANSLATED] Pollution +**Problem**: Hundreds of [UNTRANSLATED] markers from incomplete translation attempts +**Solution**: +- Only translate complete batches of manageable size +- Use analyzer that counts [UNTRANSLATED] as missing translations +- Restore from backup if pollution occurs + +#### Validation False Positives +**Problem**: Validator flags legitimate `{{variable}}` placeholders as artifacts +**Solution**: Use `--skip-validation` flag when applying batches with template variables + +#### JSON Structure Mismatches +**Problem**: Flattened dot-notation keys instead of proper nested objects +**Solution**: Use `json_beautifier.py` to restructure files to match en-GB exactly + +## Real-World Examples + +### Complete Italian Translation (Compact Method) +```bash +# Check status +python scripts/translations/translation_analyzer.py --language it-IT --summary +# Result: 46.8% complete, 1147 missing + +# Extract all entries for translation +python scripts/translations/compact_translator.py it-IT --output batch1.json + +# [Translate batch1.json with AI, save as batch1_translated.json] + +# Apply translations +python scripts/translations/translation_merger.py it-IT apply-translations --translations-file batch1_translated.json +# Result: Applied 1147 translations + +# Check progress +python scripts/translations/translation_analyzer.py --language it-IT --summary +# Result: 100% complete, 0 missing +``` + +### German Translation (Batch Method) +Starting from 46.3% completion, reaching 60.3% with batch method: + +```bash +# Initial analysis +python scripts/translations/translation_analyzer.py --language de-DE --summary +# Result: 46.3% complete, 1142 missing entries + +# Batch 1 (100 entries) +python scripts/translations/ai_translation_helper.py create-batch --languages de-DE --output de_batch_1.json --max-entries 100 +# [Translate all 100 entries in batch file] +python scripts/translations/ai_translation_helper.py apply-batch de_batch_1.json --skip-validation +# Progress: 46.6% → 51.2% + +# Continue with more batches until 100% complete +``` + +## Error Handling + +- **Missing Files**: Scripts create new files when language directories don't exist +- **Invalid JSON**: Clear error messages with line numbers +- **Placeholder Mismatches**: Validation warnings for missing or extra placeholders +- **[UNTRANSLATED] Entries**: Counted as missing translations to prevent pollution +- **Backup Failures**: Graceful handling with user notification + +## Integration with Development + +These scripts integrate with the existing translation system: +- Works with the current `frontend/public/locales/` structure +- Compatible with the i18n system used in the React frontend +- Respects the JSON format expected by the translation loader +- Maintains the nested structure required by the UI components + +## Language-Specific Notes + +### German Translation Notes +- Technical terms: Use German equivalents (PDF → PDF, API → API) +- UI actions: "hochladen" (upload), "herunterladen" (download), "speichern" (save) +- Error messages: Consistent pattern "Ein Fehler ist beim [action] aufgetreten" +- Formal address: Use "Sie" form for user-facing text + +### Italian Translation Notes +- Keep technical terms in English when commonly used (PDF, API, URL) +- Use formal address ("Lei" form) for user-facing text +- Error messages: "Si è verificato un errore durante [action]" +- UI actions: "carica" (upload), "scarica" (download), "salva" (save) + +## Common Use Cases + +1. **Complete Language Translation**: Use Compact Workflow for fastest AI-assisted translation +2. **New Language Addition**: Start with compact workflow for comprehensive coverage +3. **Updating Existing Language**: Use analyzer to find gaps, then compact or batch method +4. **Quality Assurance**: Use analyzer with `--summary` for completion metrics and issue detection +5. **External Translation Services**: Use export functionality to generate CSV files for translators +6. **Structure Maintenance**: Use json_beautifier to keep files aligned with en-GB structure \ No newline at end of file diff --git a/scripts/translations/ai_translation_helper.py b/scripts/translations/ai_translation_helper.py new file mode 100644 index 000000000..c879c229b --- /dev/null +++ b/scripts/translations/ai_translation_helper.py @@ -0,0 +1,408 @@ +#!/usr/bin/env python3 +""" +AI Translation Helper for Stirling PDF Frontend +Provides utilities for AI-assisted translation workflows including +batch processing, quality checks, and integration helpers. +""" + +import json +import os +import sys +from pathlib import Path +from typing import Dict, List, Set, Tuple, Any, Optional +import argparse +import re +from datetime import datetime +import csv + + +class AITranslationHelper: + def __init__(self, locales_dir: str = "frontend/public/locales"): + self.locales_dir = Path(locales_dir) + self.golden_truth_file = self.locales_dir / "en-GB" / "translation.json" + + def _load_json(self, file_path: Path) -> Dict: + """Load JSON file with error handling.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except (FileNotFoundError, json.JSONDecodeError) as e: + print(f"Error loading {file_path}: {e}") + return {} + + def _save_json(self, data: Dict, file_path: Path) -> None: + """Save JSON file.""" + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + def create_ai_batch_file(self, languages: List[str], output_file: Path, + max_entries_per_language: int = 50) -> None: + """Create a batch file for AI translation with multiple languages.""" + golden_truth = self._load_json(self.golden_truth_file) + batch_data = { + 'metadata': { + 'created_at': datetime.now().isoformat(), + 'source_language': 'en-GB', + 'target_languages': languages, + 'max_entries_per_language': max_entries_per_language, + 'instructions': { + 'format': 'Translate each entry maintaining JSON structure and placeholder variables like {n}, {total}, {filename}', + 'context': 'This is for a PDF manipulation tool. Keep technical terms consistent.', + 'placeholders': 'Preserve all placeholders: {n}, {total}, {filename}, etc.', + 'style': 'Keep translations concise and user-friendly' + } + }, + 'translations': {} + } + + for lang in languages: + lang_file = self.locales_dir / lang / "translation.json" + if not lang_file.exists(): + # Create empty translation structure + lang_data = {} + else: + lang_data = self._load_json(lang_file) + + # Find untranslated entries + untranslated = self._find_untranslated_entries(golden_truth, lang_data) + + # Limit entries if specified + if max_entries_per_language and len(untranslated) > max_entries_per_language: + # Prioritize by key importance + untranslated = self._prioritize_translation_keys(untranslated, max_entries_per_language) + + batch_data['translations'][lang] = {} + for key, value in untranslated.items(): + batch_data['translations'][lang][key] = { + 'original': value, + 'translated': '', # AI fills this + 'context': self._get_key_context(key) + } + + self._save_json(batch_data, output_file) + total_entries = sum(len(lang_data) for lang_data in batch_data['translations'].values()) + print(f"Created AI batch file: {output_file}") + print(f"Total entries to translate: {total_entries}") + + def _find_untranslated_entries(self, golden_truth: Dict, lang_data: Dict) -> Dict[str, str]: + """Find entries that need translation.""" + golden_flat = self._flatten_dict(golden_truth) + lang_flat = self._flatten_dict(lang_data) + + untranslated = {} + for key, value in golden_flat.items(): + if (key not in lang_flat or + lang_flat[key] == value or + (isinstance(lang_flat[key], str) and lang_flat[key].startswith("[UNTRANSLATED]"))): + if not self._is_expected_identical(key, value): + untranslated[key] = value + + return untranslated + + def _flatten_dict(self, d: Dict, parent_key: str = '', separator: str = '.') -> Dict[str, Any]: + """Flatten nested dictionary.""" + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{separator}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(self._flatten_dict(v, new_key, separator).items()) + else: + items.append((new_key, v)) + return dict(items) + + def _is_expected_identical(self, key: str, value: str) -> bool: + """Check if key should be identical across languages.""" + if str(value).strip() in ['ltr', 'rtl', 'True', 'False', 'true', 'false']: + return True + return 'language.direction' in key.lower() + + def _prioritize_translation_keys(self, untranslated: Dict[str, str], max_count: int) -> Dict[str, str]: + """Prioritize which keys to translate first based on importance.""" + # Define priority order (higher score = higher priority) + priority_patterns = [ + ('title', 10), + ('header', 9), + ('submit', 8), + ('selectText', 7), + ('prompt', 6), + ('desc', 5), + ('error', 8), + ('warning', 7), + ('save', 8), + ('download', 8), + ('upload', 7), + ] + + scored_keys = [] + for key, value in untranslated.items(): + score = 1 # base score + for pattern, pattern_score in priority_patterns: + if pattern.lower() in key.lower(): + score = max(score, pattern_score) + scored_keys.append((key, value, score)) + + # Sort by score (descending) and return top entries + scored_keys.sort(key=lambda x: x[2], reverse=True) + return {key: value for key, value, _ in scored_keys[:max_count]} + + def _get_key_context(self, key: str) -> str: + """Get contextual information for a translation key.""" + parts = key.split('.') + contexts = { + 'addPageNumbers': 'Feature for adding page numbers to PDFs', + 'compress': 'PDF compression functionality', + 'merge': 'PDF merging functionality', + 'split': 'PDF splitting functionality', + 'rotate': 'PDF rotation functionality', + 'convert': 'File conversion functionality', + 'security': 'PDF security and permissions', + 'metadata': 'PDF metadata editing', + 'watermark': 'Adding watermarks to PDFs', + 'overlay': 'PDF overlay functionality', + 'extract': 'Extracting content from PDFs' + } + + if len(parts) > 0: + main_section = parts[0] + context = contexts.get(main_section, f'Part of {main_section} functionality') + if len(parts) > 1: + context += f', specifically for {parts[-1]}' + return context + + return 'General application text' + + def validate_ai_translations(self, batch_file: Path) -> Dict[str, List[str]]: + """Validate AI translations for common issues.""" + batch_data = self._load_json(batch_file) + issues = {'errors': [], 'warnings': []} + + for lang, translations in batch_data.get('translations', {}).items(): + for key, translation_data in translations.items(): + original = translation_data.get('original', '') + translated = translation_data.get('translated', '') + + if not translated: + issues['errors'].append(f"{lang}.{key}: Missing translation") + continue + + # Check for placeholder preservation + original_placeholders = re.findall(r'\{[^}]+\}', original) + translated_placeholders = re.findall(r'\{[^}]+\}', translated) + + if set(original_placeholders) != set(translated_placeholders): + issues['warnings'].append( + f"{lang}.{key}: Placeholder mismatch - Original: {original_placeholders}, " + f"Translated: {translated_placeholders}" + ) + + # Check if translation is identical to original (might be untranslated) + if translated == original and not self._is_expected_identical(key, original): + issues['warnings'].append(f"{lang}.{key}: Translation identical to original") + + # Check for common AI translation artifacts + artifacts = ['[TRANSLATE]', '[TODO]', 'UNTRANSLATED', '{{', '}}'] + for artifact in artifacts: + if artifact in translated: + issues['errors'].append(f"{lang}.{key}: Contains translation artifact: {artifact}") + + return issues + + def apply_ai_batch_translations(self, batch_file: Path, validate: bool = True) -> Dict[str, Any]: + """Apply translations from AI batch file to individual language files.""" + batch_data = self._load_json(batch_file) + results = {'applied': {}, 'errors': [], 'warnings': []} + + if validate: + validation_issues = self.validate_ai_translations(batch_file) + if validation_issues['errors']: + print("Validation errors found. Fix these before applying:") + for error in validation_issues['errors']: + print(f" ERROR: {error}") + return results + + if validation_issues['warnings']: + print("Validation warnings (review recommended):") + for warning in validation_issues['warnings'][:10]: + print(f" WARNING: {warning}") + + for lang, translations in batch_data.get('translations', {}).items(): + lang_file = self.locales_dir / lang / "translation.json" + + # Load existing data or create new + if lang_file.exists(): + lang_data = self._load_json(lang_file) + else: + lang_data = {} + lang_file.parent.mkdir(parents=True, exist_ok=True) + + applied_count = 0 + for key, translation_data in translations.items(): + translated = translation_data.get('translated', '').strip() + if translated and translated != translation_data.get('original', ''): + self._set_nested_value(lang_data, key, translated) + applied_count += 1 + + if applied_count > 0: + self._save_json(lang_data, lang_file) + results['applied'][lang] = applied_count + print(f"Applied {applied_count} translations to {lang}") + + return results + + def _set_nested_value(self, data: Dict, key_path: str, value: Any) -> None: + """Set value in nested dict using dot notation.""" + keys = key_path.split('.') + current = data + for key in keys[:-1]: + if key not in current: + current[key] = {} + elif not isinstance(current[key], dict): + # If the current value is not a dict, we can't nest into it + print(f"Warning: Converting non-dict value at '{key}' to dict to allow nesting") + current[key] = {} + current = current[key] + current[keys[-1]] = value + + def export_for_external_translation(self, languages: List[str], output_format: str = 'csv') -> None: + """Export translations for external translation services.""" + golden_truth = self._load_json(self.golden_truth_file) + golden_flat = self._flatten_dict(golden_truth) + + if output_format == 'csv': + output_file = Path(f'translations_export_{datetime.now().strftime("%Y%m%d")}.csv') + + with open(output_file, 'w', newline='', encoding='utf-8') as csvfile: + fieldnames = ['key', 'context', 'en_GB'] + languages + writer = csv.DictWriter(csvfile, fieldnames=fieldnames) + writer.writeheader() + + for key, en_value in golden_flat.items(): + if self._is_expected_identical(key, en_value): + continue + + row = { + 'key': key, + 'context': self._get_key_context(key), + 'en_GB': en_value + } + + for lang in languages: + lang_file = self.locales_dir / lang / "translation.json" + if lang_file.exists(): + lang_data = self._load_json(lang_file) + lang_flat = self._flatten_dict(lang_data) + value = lang_flat.get(key, '') + if value.startswith('[UNTRANSLATED]'): + value = '' + row[lang] = value + else: + row[lang] = '' + + writer.writerow(row) + + print(f"Exported to {output_file}") + + elif output_format == 'json': + output_file = Path(f'translations_export_{datetime.now().strftime("%Y%m%d")}.json') + export_data = {'languages': languages, 'translations': {}} + + for key, en_value in golden_flat.items(): + if self._is_expected_identical(key, en_value): + continue + + export_data['translations'][key] = { + 'en_GB': en_value, + 'context': self._get_key_context(key) + } + + for lang in languages: + lang_file = self.locales_dir / lang / "translation.json" + if lang_file.exists(): + lang_data = self._load_json(lang_file) + lang_flat = self._flatten_dict(lang_data) + value = lang_flat.get(key, '') + if value.startswith('[UNTRANSLATED]'): + value = '' + export_data['translations'][key][lang] = value + + self._save_json(export_data, output_file) + print(f"Exported to {output_file}") + + +def main(): + parser = argparse.ArgumentParser(description='AI Translation Helper') + parser.add_argument('--locales-dir', default='frontend/public/locales', + help='Path to locales directory') + + subparsers = parser.add_subparsers(dest='command', help='Available commands') + + # Create batch command + batch_parser = subparsers.add_parser('create-batch', help='Create AI translation batch file') + batch_parser.add_argument('--languages', nargs='+', required=True, + help='Language codes to include') + batch_parser.add_argument('--output', required=True, help='Output batch file') + batch_parser.add_argument('--max-entries', type=int, default=100, + help='Max entries per language') + + # Validate command + validate_parser = subparsers.add_parser('validate', help='Validate AI translations') + validate_parser.add_argument('batch_file', help='Batch file to validate') + + # Apply command + apply_parser = subparsers.add_parser('apply-batch', help='Apply AI batch translations') + apply_parser.add_argument('batch_file', help='Batch file with translations') + apply_parser.add_argument('--skip-validation', action='store_true', + help='Skip validation before applying') + + # Export command + export_parser = subparsers.add_parser('export', help='Export for external translation') + export_parser.add_argument('--languages', nargs='+', required=True, + help='Language codes to export') + export_parser.add_argument('--format', choices=['csv', 'json'], default='csv', + help='Export format') + + args = parser.parse_args() + + if not args.command: + parser.print_help() + return + + helper = AITranslationHelper(args.locales_dir) + + if args.command == 'create-batch': + output_file = Path(args.output) + helper.create_ai_batch_file(args.languages, output_file, args.max_entries) + + elif args.command == 'validate': + batch_file = Path(args.batch_file) + issues = helper.validate_ai_translations(batch_file) + + if issues['errors']: + print("ERRORS:") + for error in issues['errors']: + print(f" - {error}") + + if issues['warnings']: + print("WARNINGS:") + for warning in issues['warnings']: + print(f" - {warning}") + + if not issues['errors'] and not issues['warnings']: + print("No validation issues found!") + + elif args.command == 'apply-batch': + batch_file = Path(args.batch_file) + results = helper.apply_ai_batch_translations( + batch_file, + validate=not args.skip_validation + ) + + total_applied = sum(results['applied'].values()) + print(f"Total translations applied: {total_applied}") + + elif args.command == 'export': + helper.export_for_external_translation(args.languages, args.format) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/translations/compact_translator.py b/scripts/translations/compact_translator.py new file mode 100644 index 000000000..59efcbe9c --- /dev/null +++ b/scripts/translations/compact_translator.py @@ -0,0 +1,177 @@ +#!/usr/bin/env python3 +""" +Compact Translation Extractor for Character-Limited AI Translation +Outputs untranslated entries in minimal JSON format with whitespace stripped. +""" + +import json +import sys +from pathlib import Path +import argparse +try: + import tomllib # Python 3.11+ +except ImportError: + try: + import toml as tomllib_fallback + tomllib = None + except ImportError: + tomllib = None + tomllib_fallback = None + + +class CompactTranslationExtractor: + def __init__(self, locales_dir: str = "frontend/public/locales", ignore_file: str = "scripts/ignore_translation.toml"): + self.locales_dir = Path(locales_dir) + self.golden_truth_file = self.locales_dir / "en-GB" / "translation.json" + self.golden_truth = self._load_json(self.golden_truth_file) + self.ignore_file = Path(ignore_file) + self.ignore_patterns = self._load_ignore_patterns() + + def _load_json(self, file_path: Path) -> dict: + """Load JSON file with error handling.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: File not found: {file_path}", file=sys.stderr) + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in {file_path}: {e}", file=sys.stderr) + sys.exit(1) + + def _load_ignore_patterns(self) -> dict: + """Load ignore patterns from TOML file.""" + if not self.ignore_file.exists(): + return {} + + try: + if tomllib: + with open(self.ignore_file, 'rb') as f: + ignore_data = tomllib.load(f) + elif tomllib_fallback: + ignore_data = tomllib_fallback.load(self.ignore_file) + else: + ignore_data = self._parse_simple_toml() + + return {lang: set(data.get('ignore', [])) for lang, data in ignore_data.items()} + except Exception as e: + print(f"Warning: Could not load ignore file {self.ignore_file}: {e}", file=sys.stderr) + return {} + + def _parse_simple_toml(self) -> dict: + """Simple TOML parser for ignore patterns (fallback).""" + ignore_data = {} + current_section = None + + with open(self.ignore_file, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + + if line.startswith('[') and line.endswith(']'): + current_section = line[1:-1] + ignore_data[current_section] = {'ignore': []} + elif line.strip().startswith("'") and current_section: + item = line.strip().strip("',") + if item: + ignore_data[current_section]['ignore'].append(item) + + return ignore_data + + def _flatten_dict(self, d: dict, parent_key: str = '', separator: str = '.') -> dict: + """Flatten nested dictionary into dot-notation keys.""" + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{separator}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(self._flatten_dict(v, new_key, separator).items()) + else: + items.append((new_key, str(v))) + return dict(items) + + def get_untranslated_entries(self, language: str) -> dict: + """Get all untranslated entries for a language in compact format.""" + target_file = self.locales_dir / language / "translation.json" + + if not target_file.exists(): + print(f"Error: Translation file not found for language: {language}", file=sys.stderr) + sys.exit(1) + + target_data = self._load_json(target_file) + golden_flat = self._flatten_dict(self.golden_truth) + target_flat = self._flatten_dict(target_data) + + lang_code = language.replace('-', '_') + ignore_set = self.ignore_patterns.get(lang_code, set()) + + # Find missing translations + missing_keys = set(golden_flat.keys()) - set(target_flat.keys()) - ignore_set + + # Find untranslated entries (identical to en-GB or marked [UNTRANSLATED]) + untranslated_keys = set() + for key in target_flat: + if key in golden_flat and key not in ignore_set: + target_value = target_flat[key] + golden_value = golden_flat[key] + + if (isinstance(target_value, str) and target_value.startswith("[UNTRANSLATED]")) or \ + (golden_value == target_value and not self._is_expected_identical(key, golden_value)): + untranslated_keys.add(key) + + # Combine and create compact output + all_untranslated = missing_keys | untranslated_keys + + compact_entries = {} + for key in sorted(all_untranslated): + if key in golden_flat: + compact_entries[key] = golden_flat[key] + + return compact_entries + + def _is_expected_identical(self, key: str, value: str) -> bool: + """Check if a key-value pair is expected to be identical across languages.""" + identical_patterns = ['language.direction'] + identical_values = {'ltr', 'rtl', 'True', 'False', 'true', 'false', 'unknown'} + + if value.strip() in identical_values: + return True + + for pattern in identical_patterns: + if pattern in key.lower(): + return True + + return False + + +def main(): + parser = argparse.ArgumentParser(description='Extract untranslated entries in compact format for AI translation') + parser.add_argument('language', help='Language code (e.g., de-DE, fr-FR)') + parser.add_argument('--locales-dir', default='frontend/public/locales', help='Path to locales directory') + parser.add_argument('--ignore-file', default='scripts/ignore_translation.toml', help='Path to ignore patterns file') + parser.add_argument('--max-entries', type=int, help='Maximum number of entries to output') + parser.add_argument('--output', help='Output file (default: stdout)') + + args = parser.parse_args() + + extractor = CompactTranslationExtractor(args.locales_dir, args.ignore_file) + untranslated = extractor.get_untranslated_entries(args.language) + + if args.max_entries: + # Take first N entries + keys = list(untranslated.keys())[:args.max_entries] + untranslated = {k: untranslated[k] for k in keys} + + # Output compact JSON (no indentation, minimal whitespace) + output = json.dumps(untranslated, separators=(',', ':'), ensure_ascii=False) + + if args.output: + with open(args.output, 'w', encoding='utf-8') as f: + f.write(output) + print(f"Extracted {len(untranslated)} untranslated entries to {args.output}", file=sys.stderr) + else: + print(output) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/translations/json_beautifier.py b/scripts/translations/json_beautifier.py new file mode 100644 index 000000000..41c65afda --- /dev/null +++ b/scripts/translations/json_beautifier.py @@ -0,0 +1,262 @@ +#!/usr/bin/env python3 +""" +JSON Beautifier and Structure Fixer for Stirling PDF Frontend +Restructures translation JSON files to match en-GB structure and key order exactly. +""" + +import json +import os +import sys +from pathlib import Path +from typing import Dict, Any, List +import argparse +from collections import OrderedDict + + +class JSONBeautifier: + def __init__(self, locales_dir: str = "frontend/public/locales"): + self.locales_dir = Path(locales_dir) + self.golden_truth_file = self.locales_dir / "en-GB" / "translation.json" + self.golden_structure = self._load_json(self.golden_truth_file) + + def _load_json(self, file_path: Path) -> Dict: + """Load JSON file with error handling.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f, object_pairs_hook=OrderedDict) + except FileNotFoundError: + print(f"Error: File not found: {file_path}") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in {file_path}: {e}") + sys.exit(1) + + def _save_json(self, data: Dict, file_path: Path, backup: bool = True) -> None: + """Save JSON file with proper formatting.""" + if backup and file_path.exists(): + backup_path = file_path.with_suffix(f'.backup.restructured.json') + file_path.rename(backup_path) + print(f"Backup created: {backup_path}") + + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False, separators=(',', ': ')) + + def _flatten_dict(self, d: Dict, parent_key: str = '', separator: str = '.') -> Dict[str, Any]: + """Flatten nested dictionary into dot-notation keys.""" + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{separator}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(self._flatten_dict(v, new_key, separator).items()) + else: + items.append((new_key, v)) + return dict(items) + + def _rebuild_structure(self, flat_dict: Dict[str, Any], reference_structure: Dict) -> Dict: + """Rebuild nested structure based on reference structure and available translations.""" + def build_recursive(ref_obj: Any, current_path: str = '') -> Any: + if isinstance(ref_obj, dict): + result = OrderedDict() + for key, value in ref_obj.items(): + new_path = f"{current_path}.{key}" if current_path else key + + if new_path in flat_dict: + # Direct translation exists + if isinstance(value, dict): + # If reference is dict but we have a string, use the string + if isinstance(flat_dict[new_path], str): + result[key] = flat_dict[new_path] + else: + # Recurse into nested structure + result[key] = build_recursive(value, new_path) + else: + result[key] = flat_dict[new_path] + else: + # No direct translation, recurse to check for nested keys + if isinstance(value, dict): + nested_result = build_recursive(value, new_path) + if nested_result: # Only add if we found some translations + result[key] = nested_result + # If no translation found and it's a leaf, skip it + + return result if result else None + else: + # Leaf node - return the translation if it exists + return flat_dict.get(current_path, None) + + return build_recursive(reference_structure) or OrderedDict() + + def restructure_translation_file(self, target_file: Path) -> Dict[str, Any]: + """Restructure a translation file to match en-GB structure exactly.""" + if not target_file.exists(): + print(f"Error: Target file does not exist: {target_file}") + return {} + + # Load the target file + target_data = self._load_json(target_file) + + # Flatten the target translations + flat_target = self._flatten_dict(target_data) + + # Rebuild structure based on golden truth + restructured = self._rebuild_structure(flat_target, self.golden_structure) + + return restructured + + def beautify_and_restructure(self, target_file: Path, backup: bool = True) -> Dict[str, Any]: + """Main function to beautify and restructure a translation file.""" + lang_code = target_file.parent.name + print(f"Restructuring {lang_code} translation file...") + + # Get the restructured data + restructured_data = self.restructure_translation_file(target_file) + + # Save the restructured file + self._save_json(restructured_data, target_file, backup) + + # Analyze the results + flat_golden = self._flatten_dict(self.golden_structure) + flat_restructured = self._flatten_dict(restructured_data) + + total_keys = len(flat_golden) + preserved_keys = len(flat_restructured) + + result = { + 'language': lang_code, + 'total_reference_keys': total_keys, + 'preserved_keys': preserved_keys, + 'structure_match': self._compare_structures(self.golden_structure, restructured_data) + } + + print(f"Restructured {lang_code}: {preserved_keys}/{total_keys} keys preserved") + return result + + def _compare_structures(self, ref: Dict, target: Dict) -> Dict[str, bool]: + """Compare structures between reference and target.""" + def compare_recursive(r: Any, t: Any, path: str = '') -> List[str]: + issues = [] + + if isinstance(r, dict) and isinstance(t, dict): + # Check for missing top-level sections + ref_keys = set(r.keys()) + target_keys = set(t.keys()) + + missing_sections = ref_keys - target_keys + if missing_sections: + for section in missing_sections: + issues.append(f"Missing section: {path}.{section}" if path else section) + + # Recurse into common sections + for key in ref_keys & target_keys: + new_path = f"{path}.{key}" if path else key + issues.extend(compare_recursive(r[key], t[key], new_path)) + + return issues + + issues = compare_recursive(ref, target) + + return { + 'structures_match': len(issues) == 0, + 'issues': issues[:10], # Limit to first 10 issues + 'total_issues': len(issues) + } + + def validate_key_order(self, target_file: Path) -> Dict[str, Any]: + """Validate that keys appear in the same order as en-GB.""" + target_data = self._load_json(target_file) + + def get_key_order(obj: Dict, path: str = '') -> List[str]: + keys = [] + for key in obj.keys(): + new_path = f"{path}.{key}" if path else key + keys.append(new_path) + if isinstance(obj[key], dict): + keys.extend(get_key_order(obj[key], new_path)) + return keys + + golden_order = get_key_order(self.golden_structure) + target_order = get_key_order(target_data) + + # Find common keys and check their relative order + common_keys = set(golden_order) & set(target_order) + + golden_indices = {key: idx for idx, key in enumerate(golden_order) if key in common_keys} + target_indices = {key: idx for idx, key in enumerate(target_order) if key in common_keys} + + order_preserved = all( + golden_indices[key1] < golden_indices[key2] + for key1 in common_keys for key2 in common_keys + if golden_indices[key1] < golden_indices[key2] and target_indices[key1] < target_indices[key2] + ) + + return { + 'order_preserved': order_preserved, + 'common_keys_count': len(common_keys), + 'golden_keys_count': len(golden_order), + 'target_keys_count': len(target_order) + } + + +def main(): + parser = argparse.ArgumentParser(description='Beautify and restructure translation JSON files') + parser.add_argument('--locales-dir', default='frontend/public/locales', + help='Path to locales directory') + parser.add_argument('--language', help='Restructure specific language only') + parser.add_argument('--all-languages', action='store_true', + help='Restructure all language files') + parser.add_argument('--no-backup', action='store_true', + help='Skip backup creation') + parser.add_argument('--validate-only', action='store_true', + help='Only validate structure, do not modify files') + + args = parser.parse_args() + + beautifier = JSONBeautifier(args.locales_dir) + + if args.language: + target_file = Path(args.locales_dir) / args.language / "translation.json" + if not target_file.exists(): + print(f"Error: Translation file not found for language: {args.language}") + sys.exit(1) + + if args.validate_only: + order_result = beautifier.validate_key_order(target_file) + print(f"Key order validation for {args.language}:") + print(f" Order preserved: {order_result['order_preserved']}") + print(f" Common keys: {order_result['common_keys_count']}/{order_result['golden_keys_count']}") + else: + result = beautifier.beautify_and_restructure(target_file, backup=not args.no_backup) + print(f"\nResults for {result['language']}:") + print(f" Keys preserved: {result['preserved_keys']}/{result['total_reference_keys']}") + if result['structure_match']['total_issues'] > 0: + print(f" Structure issues: {result['structure_match']['total_issues']}") + for issue in result['structure_match']['issues']: + print(f" - {issue}") + + elif args.all_languages: + results = [] + for lang_dir in Path(args.locales_dir).iterdir(): + if lang_dir.is_dir() and lang_dir.name != "en-GB": + translation_file = lang_dir / "translation.json" + if translation_file.exists(): + if args.validate_only: + order_result = beautifier.validate_key_order(translation_file) + print(f"{lang_dir.name}: Order preserved = {order_result['order_preserved']}") + else: + result = beautifier.beautify_and_restructure(translation_file, backup=not args.no_backup) + results.append(result) + + if not args.validate_only and results: + print(f"\n{'='*60}") + print("RESTRUCTURING SUMMARY") + print(f"{'='*60}") + for result in sorted(results, key=lambda x: x['language']): + print(f"{result['language']}: {result['preserved_keys']}/{result['total_reference_keys']} keys " + f"({result['preserved_keys']/result['total_reference_keys']*100:.1f}%)") + + else: + parser.print_help() + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/translations/translation_analyzer.py b/scripts/translations/translation_analyzer.py new file mode 100644 index 000000000..9c8315b53 --- /dev/null +++ b/scripts/translations/translation_analyzer.py @@ -0,0 +1,314 @@ +#!/usr/bin/env python3 +""" +Translation Analyzer for Stirling PDF Frontend +Compares language files against en-GB golden truth file. +""" + +import json +import os +import sys +from pathlib import Path +from typing import Dict, List, Set, Tuple +import argparse +try: + import tomllib # Python 3.11+ +except ImportError: + try: + import toml as tomllib_fallback + tomllib = None + except ImportError: + tomllib = None + tomllib_fallback = None + + +class TranslationAnalyzer: + def __init__(self, locales_dir: str = "frontend/public/locales", ignore_file: str = "scripts/ignore_translation.toml"): + self.locales_dir = Path(locales_dir) + self.golden_truth_file = self.locales_dir / "en-GB" / "translation.json" + self.golden_truth = self._load_json(self.golden_truth_file) + self.ignore_file = Path(ignore_file) + self.ignore_patterns = self._load_ignore_patterns() + + def _load_json(self, file_path: Path) -> Dict: + """Load JSON file with error handling.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: File not found: {file_path}") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in {file_path}: {e}") + sys.exit(1) + + def _load_ignore_patterns(self) -> Dict[str, Set[str]]: + """Load ignore patterns from TOML file.""" + if not self.ignore_file.exists(): + return {} + + try: + if tomllib: + # Use Python 3.11+ built-in + with open(self.ignore_file, 'rb') as f: + ignore_data = tomllib.load(f) + elif tomllib_fallback: + # Use toml library fallback + ignore_data = tomllib_fallback.load(self.ignore_file) + else: + # Simple parser as fallback + ignore_data = self._parse_simple_toml() + + # Convert lists to sets for faster lookup + return {lang: set(patterns) for lang, data in ignore_data.items() + for patterns in [data.get('ignore', [])] if patterns} + except Exception as e: + print(f"Warning: Could not load ignore file {self.ignore_file}: {e}") + return {} + + def _parse_simple_toml(self) -> Dict: + """Simple TOML parser for ignore patterns (fallback).""" + ignore_data = {} + current_section = None + + with open(self.ignore_file, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + + if line.startswith('[') and line.endswith(']'): + current_section = line[1:-1] + ignore_data[current_section] = {'ignore': []} + elif line.startswith('ignore = [') and current_section: + # Handle ignore array + continue + elif line.strip().startswith("'") and current_section: + # Extract quoted items + item = line.strip().strip("',") + if item: + ignore_data[current_section]['ignore'].append(item) + + return ignore_data + + def _flatten_dict(self, d: Dict, parent_key: str = '', separator: str = '.') -> Dict[str, str]: + """Flatten nested dictionary into dot-notation keys.""" + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{separator}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(self._flatten_dict(v, new_key, separator).items()) + else: + items.append((new_key, str(v))) + return dict(items) + + def get_all_language_files(self) -> List[Path]: + """Get all translation.json files except en-GB.""" + files = [] + for lang_dir in self.locales_dir.iterdir(): + if lang_dir.is_dir() and lang_dir.name != "en-GB": + translation_file = lang_dir / "translation.json" + if translation_file.exists(): + files.append(translation_file) + return sorted(files) + + def find_missing_translations(self, target_file: Path) -> Set[str]: + """Find keys that exist in en-GB but missing in target file.""" + target_data = self._load_json(target_file) + + golden_flat = self._flatten_dict(self.golden_truth) + target_flat = self._flatten_dict(target_data) + + missing = set(golden_flat.keys()) - set(target_flat.keys()) + + # Filter out ignored keys + lang_code = target_file.parent.name.replace('-', '_') + ignore_set = self.ignore_patterns.get(lang_code, set()) + return missing - ignore_set + + def find_untranslated_entries(self, target_file: Path) -> Set[str]: + """Find entries that appear to be untranslated (identical to en-GB).""" + target_data = self._load_json(target_file) + + golden_flat = self._flatten_dict(self.golden_truth) + target_flat = self._flatten_dict(target_data) + + lang_code = target_file.parent.name.replace('-', '_') + ignore_set = self.ignore_patterns.get(lang_code, set()) + + untranslated = set() + for key in target_flat: + if key in golden_flat: + target_value = target_flat[key] + golden_value = golden_flat[key] + + # Check if marked as [UNTRANSLATED] or identical to en-GB + if (isinstance(target_value, str) and target_value.startswith("[UNTRANSLATED]")) or \ + (golden_value == target_value and key not in ignore_set and not self._is_expected_identical(key, golden_value)): + untranslated.add(key) + + return untranslated + + def _is_expected_identical(self, key: str, value: str) -> bool: + """Check if a key-value pair is expected to be identical across languages.""" + # Keys that should be identical across languages + identical_patterns = [ + 'language.direction', + 'true', 'false', + 'unknown' + ] + + # Values that are often identical (numbers, symbols, etc.) + if value.strip() in ['ltr', 'rtl', 'True', 'False']: + return True + + # Check for patterns + for pattern in identical_patterns: + if pattern in key.lower(): + return True + + return False + + def find_extra_translations(self, target_file: Path) -> Set[str]: + """Find keys that exist in target file but not in en-GB.""" + target_data = self._load_json(target_file) + + golden_flat = self._flatten_dict(self.golden_truth) + target_flat = self._flatten_dict(target_data) + + return set(target_flat.keys()) - set(golden_flat.keys()) + + def analyze_file(self, target_file: Path) -> Dict: + """Complete analysis of a single translation file.""" + lang_code = target_file.parent.name + + missing = self.find_missing_translations(target_file) + untranslated = self.find_untranslated_entries(target_file) + extra = self.find_extra_translations(target_file) + + target_data = self._load_json(target_file) + golden_flat = self._flatten_dict(self.golden_truth) + target_flat = self._flatten_dict(target_data) + + # Calculate completion rate excluding ignored keys + lang_code = target_file.parent.name.replace('-', '_') + ignore_set = self.ignore_patterns.get(lang_code, set()) + + relevant_keys = set(golden_flat.keys()) - ignore_set + total_keys = len(relevant_keys) + + # Count keys that exist and are properly translated (not [UNTRANSLATED]) + properly_translated = 0 + for key in relevant_keys: + if key in target_flat: + value = target_flat[key] + if not (isinstance(value, str) and value.startswith("[UNTRANSLATED]")): + if key not in untranslated: # Not identical to en-GB (unless expected) + properly_translated += 1 + + completion_rate = (properly_translated / total_keys) * 100 if total_keys > 0 else 0 + + return { + 'language': lang_code, + 'file': target_file, + 'missing_count': len(missing), + 'missing_keys': sorted(missing), + 'untranslated_count': len(untranslated), + 'untranslated_keys': sorted(untranslated), + 'extra_count': len(extra), + 'extra_keys': sorted(extra), + 'total_keys': total_keys, + 'completion_rate': completion_rate + } + + def analyze_all_files(self) -> List[Dict]: + """Analyze all translation files.""" + results = [] + for file_path in self.get_all_language_files(): + results.append(self.analyze_file(file_path)) + return sorted(results, key=lambda x: x['language']) + + +def main(): + parser = argparse.ArgumentParser(description='Analyze translation files against en-GB golden truth') + parser.add_argument('--locales-dir', default='frontend/public/locales', + help='Path to locales directory') + parser.add_argument('--ignore-file', default='scripts/ignore_translation.toml', + help='Path to ignore patterns TOML file') + parser.add_argument('--language', help='Analyze specific language only') + parser.add_argument('--missing-only', action='store_true', + help='Show only missing translations') + parser.add_argument('--untranslated-only', action='store_true', + help='Show only untranslated entries') + parser.add_argument('--summary', action='store_true', + help='Show summary statistics only') + parser.add_argument('--format', choices=['text', 'json'], default='text', + help='Output format') + + args = parser.parse_args() + + analyzer = TranslationAnalyzer(args.locales_dir, args.ignore_file) + + if args.language: + target_file = Path(args.locales_dir) / args.language / "translation.json" + if not target_file.exists(): + print(f"Error: Translation file not found for language: {args.language}") + sys.exit(1) + results = [analyzer.analyze_file(target_file)] + else: + results = analyzer.analyze_all_files() + + if args.format == 'json': + print(json.dumps(results, indent=2, default=str)) + return + + # Text format output + for result in results: + lang = result['language'] + print(f"\n{'='*60}") + print(f"Language: {lang}") + print(f"File: {result['file']}") + print(f"Completion Rate: {result['completion_rate']:.1f}%") + print(f"Total Keys in en-GB: {result['total_keys']}") + + if not args.summary: + if not args.untranslated_only: + print(f"\nMissing Translations ({result['missing_count']}):") + for key in result['missing_keys'][:10]: # Show first 10 + print(f" - {key}") + if len(result['missing_keys']) > 10: + print(f" ... and {len(result['missing_keys']) - 10} more") + + if not args.missing_only: + print(f"\nUntranslated Entries ({result['untranslated_count']}):") + for key in result['untranslated_keys'][:10]: # Show first 10 + print(f" - {key}") + if len(result['untranslated_keys']) > 10: + print(f" ... and {len(result['untranslated_keys']) - 10} more") + + if result['extra_count'] > 0: + print(f"\nExtra Keys Not in en-GB ({result['extra_count']}):") + for key in result['extra_keys'][:5]: + print(f" - {key}") + if len(result['extra_keys']) > 5: + print(f" ... and {len(result['extra_keys']) - 5} more") + + print(f"\n{'='*60}") + print("SUMMARY") + print(f"{'='*60}") + avg_completion = sum(r['completion_rate'] for r in results) / len(results) if results else 0 + print(f"Average Completion Rate: {avg_completion:.1f}%") + print(f"Languages Analyzed: {len(results)}") + + # Top languages by completion + sorted_by_completion = sorted(results, key=lambda x: x['completion_rate'], reverse=True) + print(f"\nTop 5 Most Complete Languages:") + for result in sorted_by_completion[:5]: + print(f" {result['language']}: {result['completion_rate']:.1f}%") + + print(f"\nBottom 5 Languages Needing Attention:") + for result in sorted_by_completion[-5:]: + print(f" {result['language']}: {result['completion_rate']:.1f}% ({result['missing_count']} missing, {result['untranslated_count']} untranslated)") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/scripts/translations/translation_merger.py b/scripts/translations/translation_merger.py new file mode 100644 index 000000000..84884d946 --- /dev/null +++ b/scripts/translations/translation_merger.py @@ -0,0 +1,371 @@ +#!/usr/bin/env python3 +""" +Translation Merger for Stirling PDF Frontend +Merges missing translations from en-GB into target language files. +Useful for AI-assisted translation workflows. +""" + +import json +import os +import sys +from pathlib import Path +from typing import Dict, List, Set, Tuple, Any +import argparse +import shutil +from datetime import datetime + +try: + import tomllib # Python 3.11+ +except ImportError: + try: + import toml as tomllib_fallback + tomllib = None + except ImportError: + tomllib = None + tomllib_fallback = None + + +class TranslationMerger: + def __init__(self, locales_dir: str = "frontend/public/locales", ignore_file: str = "scripts/ignore_translation.toml"): + self.locales_dir = Path(locales_dir) + self.golden_truth_file = self.locales_dir / "en-GB" / "translation.json" + self.golden_truth = self._load_json(self.golden_truth_file) + self.ignore_file = Path(ignore_file) + self.ignore_patterns = self._load_ignore_patterns() + + def _load_json(self, file_path: Path) -> Dict: + """Load JSON file with error handling.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + return json.load(f) + except FileNotFoundError: + print(f"Error: File not found: {file_path}") + sys.exit(1) + except json.JSONDecodeError as e: + print(f"Error: Invalid JSON in {file_path}: {e}") + sys.exit(1) + + def _save_json(self, data: Dict, file_path: Path, backup: bool = True) -> None: + """Save JSON file with backup option.""" + if backup and file_path.exists(): + backup_path = file_path.with_suffix(f'.backup.{datetime.now().strftime("%Y%m%d_%H%M%S")}.json') + shutil.copy2(file_path, backup_path) + print(f"Backup created: {backup_path}") + + with open(file_path, 'w', encoding='utf-8') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + + def _load_ignore_patterns(self) -> Dict[str, Set[str]]: + """Load ignore patterns from TOML file.""" + if not self.ignore_file.exists(): + return {} + + try: + # Simple parser for ignore patterns + ignore_data = {} + current_section = None + + with open(self.ignore_file, 'r', encoding='utf-8') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + + if line.startswith('[') and line.endswith(']'): + current_section = line[1:-1] + ignore_data[current_section] = set() + elif line.strip().startswith("'") and current_section: + # Extract quoted items + item = line.strip().strip("',") + if item: + ignore_data[current_section].add(item) + + return ignore_data + except Exception as e: + print(f"Warning: Could not load ignore file {self.ignore_file}: {e}") + return {} + + def _get_nested_value(self, data: Dict, key_path: str) -> Any: + """Get value from nested dict using dot notation.""" + keys = key_path.split('.') + current = data + for key in keys: + if isinstance(current, dict) and key in current: + current = current[key] + else: + return None + return current + + def _set_nested_value(self, data: Dict, key_path: str, value: Any) -> None: + """Set value in nested dict using dot notation.""" + keys = key_path.split('.') + current = data + for key in keys[:-1]: + if key not in current: + current[key] = {} + elif not isinstance(current[key], dict): + # If the current value is not a dict, we can't nest into it + # This handles cases where a key exists as a string but we need to make it a dict + print(f"Warning: Converting non-dict value at '{key}' to dict to allow nesting") + current[key] = {} + current = current[key] + current[keys[-1]] = value + + def _flatten_dict(self, d: Dict, parent_key: str = '', separator: str = '.') -> Dict[str, Any]: + """Flatten nested dictionary into dot-notation keys.""" + items = [] + for k, v in d.items(): + new_key = f"{parent_key}{separator}{k}" if parent_key else k + if isinstance(v, dict): + items.extend(self._flatten_dict(v, new_key, separator).items()) + else: + items.append((new_key, v)) + return dict(items) + + def get_missing_keys(self, target_file: Path) -> List[str]: + """Get list of missing keys in target file.""" + lang_code = target_file.parent.name.replace('-', '_') + ignore_set = self.ignore_patterns.get(lang_code, set()) + + if not target_file.exists(): + golden_keys = set(self._flatten_dict(self.golden_truth).keys()) + return sorted(golden_keys - ignore_set) + + target_data = self._load_json(target_file) + golden_flat = self._flatten_dict(self.golden_truth) + target_flat = self._flatten_dict(target_data) + + missing = set(golden_flat.keys()) - set(target_flat.keys()) + return sorted(missing - ignore_set) + + def add_missing_translations(self, target_file: Path, keys_to_add: List[str] = None, + mark_untranslated: bool = True) -> Dict: + """Add missing translations from en-GB to target file.""" + if not target_file.exists(): + target_data = {} + else: + target_data = self._load_json(target_file) + + golden_flat = self._flatten_dict(self.golden_truth) + missing_keys = keys_to_add or self.get_missing_keys(target_file) + + added_count = 0 + for key in missing_keys: + if key in golden_flat: + value = golden_flat[key] + if mark_untranslated and isinstance(value, str): + # Mark as untranslated for AI to translate later + value = f"[UNTRANSLATED] {value}" + + self._set_nested_value(target_data, key, value) + added_count += 1 + + return { + 'added_count': added_count, + 'missing_keys': missing_keys, + 'data': target_data + } + + def extract_untranslated_entries(self, target_file: Path, output_file: Path = None) -> Dict: + """Extract entries marked as untranslated or identical to en-GB for AI translation.""" + if not target_file.exists(): + print(f"Error: Target file does not exist: {target_file}") + return {} + + target_data = self._load_json(target_file) + golden_flat = self._flatten_dict(self.golden_truth) + target_flat = self._flatten_dict(target_data) + + untranslated_entries = {} + + for key, value in target_flat.items(): + if key in golden_flat: + golden_value = golden_flat[key] + + # Check if marked as untranslated + if isinstance(value, str) and value.startswith("[UNTRANSLATED]"): + untranslated_entries[key] = { + 'original': golden_value, + 'current': value, + 'reason': 'marked_untranslated' + } + # Check if identical to golden (and should be translated) + elif value == golden_value and not self._is_expected_identical(key, value): + untranslated_entries[key] = { + 'original': golden_value, + 'current': value, + 'reason': 'identical_to_english' + } + + if output_file: + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(untranslated_entries, f, indent=2, ensure_ascii=False) + + return untranslated_entries + + def _is_expected_identical(self, key: str, value: str) -> bool: + """Check if a key-value pair is expected to be identical across languages.""" + identical_patterns = [ + 'language.direction', + ] + + if str(value).strip() in ['ltr', 'rtl', 'True', 'False', 'true', 'false']: + return True + + for pattern in identical_patterns: + if pattern in key.lower(): + return True + + return False + + def apply_translations(self, target_file: Path, translations: Dict[str, str], + backup: bool = True) -> Dict: + """Apply provided translations to target file.""" + if not target_file.exists(): + print(f"Error: Target file does not exist: {target_file}") + return {'success': False, 'error': 'File not found'} + + target_data = self._load_json(target_file) + applied_count = 0 + errors = [] + + for key, translation in translations.items(): + try: + # Remove [UNTRANSLATED] marker if present + if translation.startswith("[UNTRANSLATED]"): + translation = translation.replace("[UNTRANSLATED]", "").strip() + + self._set_nested_value(target_data, key, translation) + applied_count += 1 + except Exception as e: + errors.append(f"Error setting {key}: {e}") + + if applied_count > 0: + self._save_json(target_data, target_file, backup) + + return { + 'success': True, + 'applied_count': applied_count, + 'errors': errors, + 'data': target_data + } + + def create_translation_template(self, target_file: Path, output_file: Path) -> None: + """Create a template file for AI translation with context.""" + untranslated = self.extract_untranslated_entries(target_file) + + template = { + 'metadata': { + 'source_language': 'en-GB', + 'target_language': target_file.parent.name, + 'total_entries': len(untranslated), + 'created_at': datetime.now().isoformat(), + 'instructions': 'Translate the "original" values to the target language. Keep the same keys.' + }, + 'translations': {} + } + + for key, entry in untranslated.items(): + template['translations'][key] = { + 'original': entry['original'], + 'translated': '', # AI should fill this + 'context': self._get_context_for_key(key), + 'reason': entry['reason'] + } + + with open(output_file, 'w', encoding='utf-8') as f: + json.dump(template, f, indent=2, ensure_ascii=False) + + print(f"Translation template created: {output_file}") + print(f"Contains {len(untranslated)} entries to translate") + + def _get_context_for_key(self, key: str) -> str: + """Get context information for a translation key.""" + parts = key.split('.') + if len(parts) >= 2: + return f"Section: {parts[0]}, Property: {parts[-1]}" + return f"Property: {parts[-1]}" + + +def main(): + parser = argparse.ArgumentParser(description='Merge and manage translation files') + parser.add_argument('--locales-dir', default='frontend/public/locales', + help='Path to locales directory') + parser.add_argument('--ignore-file', default='scripts/ignore_translation.toml', + help='Path to ignore patterns TOML file') + parser.add_argument('language', help='Target language code (e.g., fr-FR)') + + subparsers = parser.add_subparsers(dest='command', help='Available commands') + + # Add missing command + add_parser = subparsers.add_parser('add-missing', help='Add missing translations from en-GB') + add_parser.add_argument('--no-backup', action='store_true', help='Skip backup creation') + add_parser.add_argument('--mark-untranslated', action='store_true', default=True, + help='Mark added translations as [UNTRANSLATED]') + + # Extract untranslated command + extract_parser = subparsers.add_parser('extract-untranslated', help='Extract untranslated entries') + extract_parser.add_argument('--output', help='Output file path') + + # Create template command + template_parser = subparsers.add_parser('create-template', help='Create AI translation template') + template_parser.add_argument('--output', required=True, help='Output template file path') + + # Apply translations command + apply_parser = subparsers.add_parser('apply-translations', help='Apply translations from JSON file') + apply_parser.add_argument('--translations-file', required=True, help='JSON file with translations') + apply_parser.add_argument('--no-backup', action='store_true', help='Skip backup creation') + + args = parser.parse_args() + + if not args.command: + parser.print_help() + return + + merger = TranslationMerger(args.locales_dir, args.ignore_file) + target_file = Path(args.locales_dir) / args.language / "translation.json" + + if args.command == 'add-missing': + print(f"Adding missing translations to {args.language}...") + result = merger.add_missing_translations( + target_file, + mark_untranslated=args.mark_untranslated + ) + + merger._save_json(result['data'], target_file, backup=not args.no_backup) + print(f"Added {result['added_count']} missing translations") + + elif args.command == 'extract-untranslated': + output_file = Path(args.output) if args.output else target_file.with_suffix('.untranslated.json') + untranslated = merger.extract_untranslated_entries(target_file, output_file) + print(f"Extracted {len(untranslated)} untranslated entries to {output_file}") + + elif args.command == 'create-template': + output_file = Path(args.output) + merger.create_translation_template(target_file, output_file) + + elif args.command == 'apply-translations': + with open(args.translations_file, 'r', encoding='utf-8') as f: + translations_data = json.load(f) + + # Extract translations from template format or simple dict + if 'translations' in translations_data: + translations = {k: v['translated'] for k, v in translations_data['translations'].items() + if v.get('translated')} + else: + translations = translations_data + + result = merger.apply_translations(target_file, translations, backup=not args.no_backup) + + if result['success']: + print(f"Applied {result['applied_count']} translations") + if result['errors']: + print(f"Errors: {len(result['errors'])}") + for error in result['errors'][:5]: + print(f" - {error}") + else: + print(f"Failed: {result.get('error', 'Unknown error')}") + + +if __name__ == "__main__": + main() \ No newline at end of file