Merge branch 'main' into text-removal-auto-redact

This commit is contained in:
Balázs Szücs 2025-07-21 10:38:33 +02:00 committed by GitHub
commit 0185b40cdc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 277 additions and 264 deletions

2
.github/CODEOWNERS vendored
View File

@ -1,2 +1,2 @@
# All PRs to V1 must be approved by Frooodle # All PRs to V1 must be approved by Frooodle
* @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh * @Frooodle @reecebrowne @Ludy87 @DarioGii @ConnorYoh @EthanHealy01

View File

@ -1,29 +1,22 @@
build: &build build: &build
- build.gradle - build.gradle
- app/core/build.gradle - app/(common|core|proprietary)/build.gradle
- app/common/build.gradle
- app/proprietary/build.gradle
app: &app app: &app
- app/core/src/main/java/** - app/(common|core|proprietary)/src/main/java/**
- app/common/src/main/java/**
- app/proprietary/src/main/java/**
openapi: &openapi openapi: &openapi
- build.gradle - build.gradle
- app/core/build.gradle - app/(common|core|proprietary)/build.gradle
- app/core/src/main/java/** - app/(common|core|proprietary)/src/main/java/**
- app/common/build.gradle
- app/common/src/main/java/**
- app/proprietary/build.gradle
- app/proprietary/src/main/java/**
project: &project project: &project
- app/** - app/(common|core|proprietary)/src/(main|test)/java/**
- app/(common|core|proprietary)/build.gradle
- 'app/(common|core|proprietary)/src/(main|test)/resources/**/!(messages_*.properties|*.md)*'
- exampleYmlFiles/** - exampleYmlFiles/**
- gradle/** - gradle/**
- libs/** - libs/**
- scripts/**
- testing/** - testing/**
- build.gradle - build.gradle
- Dockerfile - Dockerfile

View File

@ -7,6 +7,7 @@
"sbplat", "sbplat",
"reecebrowne", "reecebrowne",
"DarioGii", "DarioGii",
"ConnorYoh" "ConnorYoh",
"EthanHealy01"
] ]
} }

View File

@ -76,8 +76,8 @@ labels:
- 'app/core/src/main/resources/settings.yml.template' - 'app/core/src/main/resources/settings.yml.template'
- 'app/core/src/main/resources/application.properties' - 'app/core/src/main/resources/application.properties'
- 'app/core/src/main/resources/banner.txt' - 'app/core/src/main/resources/banner.txt'
- 'scripts/png_to_webp.py' - 'app/core/src/main/resources/static/python/png_to_webp.py'
- 'split_photos.py' - 'app/core/src/main/resources/static/python/split_photos.py'
- 'application.properties' - 'application.properties'
- label: 'Security' - label: 'Security'
@ -95,8 +95,8 @@ labels:
- 'app/core/src/main/java/stirling/software/SPDF/model/api/.*' - 'app/core/src/main/java/stirling/software/SPDF/model/api/.*'
- 'app/core/src/main/java/stirling/software/SPDF/service/ApiDocService.java' - 'app/core/src/main/java/stirling/software/SPDF/service/ApiDocService.java'
- 'app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/.*' - 'app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/.*'
- 'scripts/png_to_webp.py' - 'app/core/src/main/resources/static/python/png_to_webp.py'
- 'split_photos.py' - 'app/core/src/main/resources/static/python/split_photos.py'
- '.github/workflows/swagger.yml' - '.github/workflows/swagger.yml'
- label: 'Documentation' - label: 'Documentation'

View File

@ -180,7 +180,6 @@ jobs:
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- name: Run Gradle Command - name: Run Gradle Command
run: | run: |

View File

@ -11,6 +11,24 @@ permissions:
contents: read contents: read
jobs: jobs:
files-changed:
name: detect what files changed
runs-on: ubuntu-latest
timeout-minutes: 3
# Map a step output to a job output
outputs:
build: ${{ steps.changes.outputs.build }}
app: ${{ steps.changes.outputs.app }}
project: ${{ steps.changes.outputs.project }}
openapi: ${{ steps.changes.outputs.openapi }}
steps:
- uses: actions/checkout@7884fcad6b5d53d10323aee724dc68d8b9096a2e # v2
- name: Check for file changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: changes
with:
filters: ".github/config/.files.yaml"
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -38,7 +56,6 @@ jobs:
with: with:
java-version: ${{ matrix.jdk-version }} java-version: ${{ matrix.jdk-version }}
distribution: "temurin" distribution: "temurin"
cache: gradle
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
@ -95,7 +112,8 @@ jobs:
if-no-files-found: warn if-no-files-found: warn
check-generateOpenApiDocs: check-generateOpenApiDocs:
needs: build if: needs.files-changed.outputs.openapi == 'true'
needs: [files-changed, build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
@ -106,37 +124,27 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Filter for integration changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: files
with:
filters: ".github/config/.files.yaml"
- name: Set up JDK 17 - name: Set up JDK 17
if: ${{ steps.files.outputs.openapi == 'true' }}
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- name: Setup Gradle - name: Setup Gradle
if: ${{ steps.files.outputs.openapi == 'true' }}
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
- name: Generate OpenAPI documentation - name: Generate OpenAPI documentation
if: ${{ steps.files.outputs.openapi == 'true' }}
run: ./gradlew :stirling-pdf:generateOpenApiDocs run: ./gradlew :stirling-pdf:generateOpenApiDocs
- name: Upload OpenAPI Documentation - name: Upload OpenAPI Documentation
if: ${{ steps.files.outputs.openapi == 'true' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with: with:
name: openapi-docs name: openapi-docs
path: ./SwaggerDoc.json path: ./SwaggerDoc.json
check-licence: check-licence:
needs: build if: needs.files-changed.outputs.build == 'true'
needs: [files-changed, build]
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Harden Runner - name: Harden Runner
@ -147,22 +155,13 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Filter for integration changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: files
with:
filters: ".github/config/.files.yaml"
- name: Set up JDK 17 - name: Set up JDK 17
if: ${{ steps.files.outputs.build == 'true' }}
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- name: check the licenses for compatibility - name: check the licenses for compatibility
if: ${{ steps.files.outputs.build == 'true' }}
run: ./gradlew clean checkLicense run: ./gradlew clean checkLicense
- name: FAILED - check the licenses for compatibility - name: FAILED - check the licenses for compatibility
@ -175,6 +174,8 @@ jobs:
retention-days: 3 retention-days: 3
docker-compose-tests: docker-compose-tests:
if: needs.files-changed.outputs.project == 'true'
needs: files-changed
# if: github.event_name == 'push' && github.ref == 'refs/heads/main' || # if: github.event_name == 'push' && github.ref == 'refs/heads/main' ||
# (github.event_name == 'pull_request' && # (github.event_name == 'pull_request' &&
# contains(github.event.pull_request.labels.*.name, 'licenses') == false && # contains(github.event.pull_request.labels.*.name, 'licenses') == false &&
@ -200,32 +201,21 @@ jobs:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Filter for integration changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: files
with:
filters: ".github/config/.files.yaml"
- name: Set up Java 17 - name: Set up Java 17
if: ${{ steps.files.outputs.project == 'true' }}
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- name: Set up Docker Buildx - name: Set up Docker Buildx
if: ${{ steps.files.outputs.project == 'true' }}
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Install Docker Compose - name: Install Docker Compose
if: ${{ steps.files.outputs.project == 'true' }}
run: | run: |
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.37.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo curl -SL "https://github.com/docker/compose/releases/download/v2.37.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose
- name: Set up Python - name: Set up Python
if: ${{ steps.files.outputs.project == 'true' }}
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0 uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
with: with:
python-version: "3.12" python-version: "3.12"
@ -233,12 +223,10 @@ jobs:
cache-dependency-path: ./testing/cucumber/requirements.txt cache-dependency-path: ./testing/cucumber/requirements.txt
- name: Pip requirements - name: Pip requirements
if: ${{ steps.files.outputs.project == 'true' }}
run: | run: |
pip install --require-hashes -r ./testing/cucumber/requirements.txt pip install --require-hashes -r ./testing/cucumber/requirements.txt
- name: Run Docker Compose Tests - name: Run Docker Compose Tests
if: ${{ steps.files.outputs.project == 'true' }}
run: | run: |
chmod +x ./testing/test_webpages.sh chmod +x ./testing/test_webpages.sh
chmod +x ./testing/test.sh chmod +x ./testing/test.sh
@ -246,8 +234,8 @@ jobs:
./testing/test.sh ./testing/test.sh
test-build-docker-images: test-build-docker-images:
if: github.event_name == 'pull_request' if: github.event_name == 'pull_request' && needs.files-changed.outputs.project == 'true'
needs: [build, check-generateOpenApiDocs, check-licence] needs: [files-changed, build, check-generateOpenApiDocs, check-licence]
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
fail-fast: false fail-fast: false
@ -262,44 +250,31 @@ jobs:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
- name: Filter for integration changes
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
id: files
with:
filters: ".github/config/.files.yaml"
- name: Set up JDK 17 - name: Set up JDK 17
if: ${{ steps.files.outputs.project == 'true' }}
uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- name: Set up Gradle - name: Set up Gradle
if: ${{ steps.files.outputs.project == 'true' }}
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with: with:
gradle-version: 8.14 gradle-version: 8.14
- name: Build application - name: Build application
if: ${{ steps.files.outputs.project == 'true' }}
run: ./gradlew clean build run: ./gradlew clean build
env: env:
DISABLE_ADDITIONAL_FEATURES: true DISABLE_ADDITIONAL_FEATURES: true
STIRLING_PDF_DESKTOP_UI: false STIRLING_PDF_DESKTOP_UI: false
- name: Set up QEMU - name: Set up QEMU
if: ${{ steps.files.outputs.project == 'true' }}
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0 uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
if: ${{ steps.files.outputs.project == 'true' }}
id: buildx id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1 uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
- name: Build ${{ matrix.docker-rev }} - name: Build ${{ matrix.docker-rev }}
if: ${{ steps.files.outputs.project == 'true' }}
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0 uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with: with:
builder: ${{ steps.buildx.outputs.name }} builder: ${{ steps.buildx.outputs.name }}

View File

@ -40,7 +40,6 @@ jobs:
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1

View File

@ -32,7 +32,6 @@ jobs:
with: with:
distribution: 'temurin' distribution: 'temurin'
java-version: '21' java-version: '21'
cache: gradle
# ✅ Get version from Gradle # ✅ Get version from Gradle
- name: Get version number - name: Get version number
@ -72,7 +71,6 @@ jobs:
with: with:
java-version: "21" java-version: "21"
distribution: "temurin" distribution: "temurin"
cache: gradle
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with: with:
@ -161,7 +159,6 @@ jobs:
with: with:
java-version: "21" java-version: "21"
distribution: "temurin" distribution: "temurin"
cache: gradle
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with: with:

View File

@ -29,7 +29,6 @@ jobs:
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with: with:

View File

@ -34,7 +34,6 @@ jobs:
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1
with: with:

View File

@ -25,7 +25,6 @@ jobs:
with: with:
java-version: "17" java-version: "17"
distribution: "temurin" distribution: "temurin"
cache: gradle
- uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1

View File

@ -24,7 +24,6 @@ jobs:
with: with:
java-version: '17' java-version: '17'
distribution: 'temurin' distribution: 'temurin'
cache: gradle
- name: Setup Gradle - name: Setup Gradle
uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1

View File

@ -6,10 +6,10 @@ repos:
args: args:
- --fix - --fix
- --line-length=127 - --line-length=127
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$ files: ^((\.github/scripts|scripts|app/core/src/main/resources/static/python)/.+)?[^/]+\.py$
exclude: (split_photos.py) exclude: (split_photos.py)
- id: ruff-format - id: ruff-format
files: ^((\.github/scripts|scripts)/.+)?[^/]+\.py$ files: ^((\.github/scripts|scripts|app/core/src/main/resources/static/python)/.+)?[^/]+\.py$
exclude: (split_photos.py) exclude: (split_photos.py)
- repo: https://github.com/codespell-project/codespell - repo: https://github.com/codespell-project/codespell
rev: v2.4.1 rev: v2.4.1

View File

@ -143,7 +143,7 @@ Stirling-PDF currently supports 40 languages!
| Portuguese (Português) (pt_PT) | ![70%](https://geps.dev/progress/70) | | Portuguese (Português) (pt_PT) | ![70%](https://geps.dev/progress/70) |
| Portuguese Brazilian (Português) (pt_BR) | ![77%](https://geps.dev/progress/77) | | Portuguese Brazilian (Português) (pt_BR) | ![77%](https://geps.dev/progress/77) |
| Romanian (Română) (ro_RO) | ![59%](https://geps.dev/progress/59) | | Romanian (Română) (ro_RO) | ![59%](https://geps.dev/progress/59) |
| Russian (Русский) (ru_RU) | ![81%](https://geps.dev/progress/81) | | Russian (Русский) (ru_RU) | ![90%](https://geps.dev/progress/90) |
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![97%](https://geps.dev/progress/97) | | Serbian Latin alphabet (Srpski) (sr_LATN_RS) | ![97%](https://geps.dev/progress/97) |
| Simplified Chinese (简体中文) (zh_CN) | ![95%](https://geps.dev/progress/95) | | Simplified Chinese (简体中文) (zh_CN) | ![95%](https://geps.dev/progress/95) |
| Slovakian (Slovensky) (sk_SK) | ![53%](https://geps.dev/progress/53) | | Slovakian (Slovensky) (sk_SK) | ![53%](https://geps.dev/progress/53) |

View File

@ -14,6 +14,7 @@ public class InstallationPathConfig {
private static final String CONFIG_PATH; private static final String CONFIG_PATH;
private static final String CUSTOM_FILES_PATH; private static final String CUSTOM_FILES_PATH;
private static final String CLIENT_WEBUI_PATH; private static final String CLIENT_WEBUI_PATH;
private static final String SCRIPTS_PATH;
// Config paths // Config paths
private static final String SETTINGS_PATH; private static final String SETTINGS_PATH;
@ -36,6 +37,7 @@ public class InstallationPathConfig {
// Initialize config paths // Initialize config paths
SETTINGS_PATH = CONFIG_PATH + "settings.yml"; SETTINGS_PATH = CONFIG_PATH + "settings.yml";
CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml"; CUSTOM_SETTINGS_PATH = CONFIG_PATH + "custom_settings.yml";
SCRIPTS_PATH = CONFIG_PATH + "scripts" + File.separator;
// Initialize custom file paths // Initialize custom file paths
STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator; STATIC_PATH = CUSTOM_FILES_PATH + "static" + File.separator;
@ -89,6 +91,10 @@ public class InstallationPathConfig {
return CLIENT_WEBUI_PATH; return CLIENT_WEBUI_PATH;
} }
public static String getScriptsPath() {
return SCRIPTS_PATH;
}
public static String getSettingsPath() { public static String getSettingsPath() {
return SETTINGS_PATH; return SETTINGS_PATH;
} }

View File

@ -16,6 +16,7 @@ import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.UUID; import java.util.UUID;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader; import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils; import org.springframework.core.io.support.ResourcePatternUtils;
@ -33,6 +34,9 @@ import stirling.software.common.configuration.InstallationPathConfig;
@Slf4j @Slf4j
public class GeneralUtils { public class GeneralUtils {
private static final List<String> DEFAULT_VALID_SCRIPTS =
List.of("png_to_webp.py", "split_photos.py");
public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException { public static File convertMultipartFileToFile(MultipartFile multipartFile) throws IOException {
String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY"); String customTempDir = System.getenv("STIRLING_TEMPFILES_DIRECTORY");
if (customTempDir == null || customTempDir.isEmpty()) { if (customTempDir == null || customTempDir.isEmpty()) {
@ -442,6 +446,40 @@ public class GeneralUtils {
} }
} }
/**
* Extracts a file from classpath:/static/python to a temporary directory and returns the path.
*/
public static Path extractScript(String scriptName) throws IOException {
// Validate input
if (scriptName == null || scriptName.trim().isEmpty()) {
throw new IllegalArgumentException("scriptName must not be null or empty");
}
if (scriptName.contains("..") || scriptName.contains("/")) {
throw new IllegalArgumentException(
"scriptName must not contain path traversal characters");
}
if (!DEFAULT_VALID_SCRIPTS.contains(scriptName)) {
throw new IllegalArgumentException(
"scriptName must be either 'png_to_webp.py' or 'split_photos.py'");
}
Path scriptsDir = Paths.get(InstallationPathConfig.getScriptsPath(), "python");
Files.createDirectories(scriptsDir);
Path scriptFile = scriptsDir.resolve(scriptName);
if (!Files.exists(scriptFile)) {
ClassPathResource resource = new ClassPathResource("static/python/" + scriptName);
try (InputStream in = resource.getInputStream()) {
Files.copy(in, scriptFile, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException e) {
log.error("Failed to extract Python script", e);
throw e;
}
}
return scriptFile;
}
public static boolean isVersionHigher(String currentVersion, String compareVersion) { public static boolean isVersionHigher(String currentVersion, String compareVersion) {
if (currentVersion == null || compareVersion == null) { if (currentVersion == null || compareVersion == null) {
return false; return false;

2
app/core/.gitignore vendored
View File

@ -194,3 +194,5 @@ id_ed25519.pub
# node_modules # node_modules
node_modules/ node_modules/
scripts/**/*

View File

@ -117,10 +117,14 @@ public class ConvertImgPDFController {
} }
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand(); String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
Path pngToWebpScript = GeneralUtils.extractScript("png_to_webp.py");
List<String> command = new ArrayList<>(); List<String> command = new ArrayList<>();
command.add(pythonVersion); command.add(pythonVersion);
command.add("./scripts/png_to_webp.py"); // Python script to handle the conversion command.add(
pngToWebpScript
.toAbsolutePath()
.toString()); // Python script to handle the conversion
// Create a temporary directory for the output WebP files // Create a temporary directory for the output WebP files
tempOutputDir = Files.createTempDirectory("webp_output"); tempOutputDir = Files.createTempDirectory("webp_output");
@ -232,7 +236,8 @@ public class ConvertImgPDFController {
PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory); PdfUtils.imageToPdf(file, fitOption, autoRotate, colorType, pdfDocumentFactory);
return WebResponseUtils.bytesToWebResponse( return WebResponseUtils.bytesToWebResponse(
bytes, bytes,
new File(file[0].getOriginalFilename()).getName().replaceFirst("[.][^.]+$", "") + "_converted.pdf"); new File(file[0].getOriginalFilename()).getName().replaceFirst("[.][^.]+$", "")
+ "_converted.pdf");
} }
private String getMediaType(String imageFormat) { private String getMediaType(String imageFormat) {

View File

@ -34,6 +34,7 @@ import stirling.software.SPDF.model.api.misc.ExtractImageScansRequest;
import stirling.software.common.service.CustomPDFDocumentFactory; import stirling.software.common.service.CustomPDFDocumentFactory;
import stirling.software.common.util.CheckProgramInstall; import stirling.software.common.util.CheckProgramInstall;
import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.ExceptionUtils;
import stirling.software.common.util.GeneralUtils;
import stirling.software.common.util.ProcessExecutor; import stirling.software.common.util.ProcessExecutor;
import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult;
import stirling.software.common.util.WebResponseUtils; import stirling.software.common.util.WebResponseUtils;
@ -78,6 +79,7 @@ public class ExtractImageScansController {
} }
String pythonVersion = CheckProgramInstall.getAvailablePythonCommand(); String pythonVersion = CheckProgramInstall.getAvailablePythonCommand();
Path splitPhotosScript = GeneralUtils.extractScript("split_photos.py");
try { try {
// Check if input file is a PDF // Check if input file is a PDF
if ("pdf".equalsIgnoreCase(extension)) { if ("pdf".equalsIgnoreCase(extension)) {
@ -120,7 +122,7 @@ public class ExtractImageScansController {
new ArrayList<>( new ArrayList<>(
Arrays.asList( Arrays.asList(
pythonVersion, pythonVersion,
"./scripts/split_photos.py", splitPhotosScript.toAbsolutePath().toString(),
images.get(i), images.get(i),
tempDir.toString(), tempDir.toString(),
"--angle_threshold", "--angle_threshold",

View File

@ -221,7 +221,7 @@ error.fileNotFound=Файл с идентификатором: {0} не найд
# Database and configuration messages # Database and configuration messages
error.noBackupScripts=Сценарий резервного копирования не найден error.noBackupScripts=Сценарий резервного копирования не найден
error.unsupportedProvider={0} в настоящее время не поддерживается. error.unsupportedProvider={0} в настоящее время не поддерживается.
error.pathTraversalDetected=Path traversal detected for security reasons. error.pathTraversalDetected=Обнаружено пересечение пути по соображениям безопасности.
# Validation messages # Validation messages
error.invalidArgument=Недопустимый аргумент: {0} error.invalidArgument=Недопустимый аргумент: {0}
@ -439,8 +439,8 @@ adminUserSettings.activeUsers=Активные пользователи:
adminUserSettings.disabledUsers=Отключенные пользователи: adminUserSettings.disabledUsers=Отключенные пользователи:
adminUserSettings.totalUsers=Всего пользователей: adminUserSettings.totalUsers=Всего пользователей:
adminUserSettings.lastRequest=Последний запрос adminUserSettings.lastRequest=Последний запрос
adminUserSettings.usage=View Usage adminUserSettings.usage=Просмотр использования
adminUserSettings.teams=View/Edit Teams adminUserSettings.teams=Просмотр/редактирование групп
adminUserSettings.team=Группа adminUserSettings.team=Группа
adminUserSettings.manageTeams=Управление группами adminUserSettings.manageTeams=Управление группами
adminUserSettings.createTeam=Создать группу adminUserSettings.createTeam=Создать группу
@ -454,34 +454,34 @@ adminUserSettings.teamHidden=Скрытая
adminUserSettings.totalMembers=Общее количество участников adminUserSettings.totalMembers=Общее количество участников
adminUserSettings.confirmDeleteTeam=Вы уверены, что хотите удалить эту группу? adminUserSettings.confirmDeleteTeam=Вы уверены, что хотите удалить эту группу?
teamCreated=Team created successfully teamCreated=Группа успешно создана
teamExists=A team with that name already exists teamExists=Группа с таким названием уже существует
teamNameExists=Another team with that name already exists teamNameExists=Другая группа с таким названием уже существует
teamNotFound=Team not found teamNotFound=Группа не найдена
teamDeleted=Team deleted teamDeleted=Группа удалена
teamHasUsers=Cannot delete a team with users assigned teamHasUsers=Не удается удалить группу с назначенными пользователями
teamRenamed=Team renamed successfully teamRenamed=Команда успешно переименована
# Team user management # Team user management
team.addUser=Add User to Team team.addUser=Добавить пользователя в группу
team.selectUser=Select User team.selectUser=Выбрать пользователя
team.warning.moveUser=Warning: This will move the user from "{0}" team to "{1}" team. Are you sure? team.warning.moveUser=Внимание: Это приведет к перемещению пользователя из группы "{0}" в группу "{1}". Вы уверены?
team.confirm.moveUser=Are you sure you want to move this user from "{0}" team to "{1}" team? team.confirm.moveUser=Вы уверены, что хотите перевести этого пользователя из группы "{0}" в группу "{1}"?
team.userAdded=User successfully added to team team.userAdded=Пользователь успешно добавлен в группу
team.back=Back to Teams team.back=Назад в группы
team.internal=Internal Team team.internal=Внутренняя группа
team.internalTeamNotAccessible=The Internal team is a system team and cannot be accessed team.internalTeamNotAccessible=Внутренняя группа - это системная группа, и доступ к ней невозможен
team.cannotMoveInternalUsers=Users in the Internal team cannot be moved to other teams team.cannotMoveInternalUsers=Пользователи из внутренней группы не могут быть переведены в другие группы
team.hidden=Hidden team.hidden=Скрытая
team.name=Team Name team.name=Имя группы
team.totalMembers=Total Members team.totalMembers=Общее количество участников
team.members=Members team.members=Участники
team.username=Username team.username=Имя пользователя
team.role=Role team.role=Роль
team.status=Status team.status=Статус
team.enabled=Enabled team.enabled=Включен
team.disabled=Disabled team.disabled=Отключен
team.noMembers=This team has no members yet. team.noMembers=В этой команде пока нет участников.
@ -565,16 +565,16 @@ split.tags=операции со страницами,разделение,мн
home.rotate.title=Повернуть home.rotate.title=Повернуть
home.rotate.desc=Легко поворачивайте ваши PDF-файлы. home.rotate.desc=Легко поворачивайте ваши PDF-файлы.
rotate.tags=серверная часть rotate.tags=повернуть,наклонить
home.imageToPdf.title=Изображение в PDF home.imageToPdf.title=Изображение в PDF
home.imageToPdf.desc=Преобразование изображения (PNG, JPEG, GIF) в PDF. home.imageToPdf.desc=Преобразование изображения (PNG, JPEG, GIF) в PDF.
imageToPdf.tags=конвертация,изображение,jpg,картинка,фото imageToPdf.tags=png,jpeg,gif,конвертация,изображение,картинка,фото
home.pdfToImage.title=PDF в изображение home.pdfToImage.title=PDF в изображение
home.pdfToImage.desc=Преобразование PDF в изображение (PNG, JPEG, GIF). home.pdfToImage.desc=Преобразование PDF в изображение (PNG, JPEG, GIF).
pdfToImage.tags=конвертация,изображение,jpg,картинка,фото pdfToImage.tags=png,jpeg,gif,конвертация,изображение,картинка,фото
home.pdfOrganiser.title=Организация home.pdfOrganiser.title=Организация
home.pdfOrganiser.desc=Удаление/переупорядочивание страниц в любом порядке home.pdfOrganiser.desc=Удаление/переупорядочивание страниц в любом порядке
@ -587,7 +587,7 @@ addImage.tags=изображение,jpg,картинка,фото
home.attachments.title=Добавлять вложения home.attachments.title=Добавлять вложения
home.attachments.desc=Добавление или удаление встроенных файлов (вложений) в PDF-файл или из него home.attachments.desc=Добавление или удаление встроенных файлов (вложений) в PDF-файл или из него
attachments.tags=embed,attach,file,attachment,attachments attachments.tags=вставлять,прикреплять,файл,вложение,вложения
home.watermark.title=Добавить водяной знак home.watermark.title=Добавить водяной знак
home.watermark.desc=Добавьте собственный водяной знак в ваш PDF-документ. home.watermark.desc=Добавьте собственный водяной знак в ваш PDF-документ.
@ -615,8 +615,8 @@ home.compressPdfs.desc=Сжимайте PDF-файлы для уменьшени
compressPdfs.tags=сжатие,маленький,крошечный compressPdfs.tags=сжатие,маленький,крошечный
home.unlockPDFForms.title=Разблокировать PDF-формы home.unlockPDFForms.title=Разблокировать PDF-формы
home.unlockPDFForms.desc=Удалите свойство "только для чтения" для полей формы в PDF-документа. home.unlockPDFForms.desc=Удалите свойство 'только для чтения' для полей формы в PDF-документа.
unlockPDFForms.tags=remove,delete,form,field,readonly unlockPDFForms.tags=удалить,форма,поле,только для чтения
home.changeMetadata.title=Изменить метаданные home.changeMetadata.title=Изменить метаданные
home.changeMetadata.desc=Изменить/удалить/добавить метаданные из PDF-документа home.changeMetadata.desc=Изменить/удалить/добавить метаданные из PDF-документа
@ -644,15 +644,15 @@ PDFToWord.tags=doc,docx,odt,word,преобразование,формат,ко
home.PDFToPresentation.title=PDF в презентацию home.PDFToPresentation.title=PDF в презентацию
home.PDFToPresentation.desc=Преобразование PDF в форматы презентаций (PPT, PPTX и ODP) home.PDFToPresentation.desc=Преобразование PDF в форматы презентаций (PPT, PPTX и ODP)
PDFToPresentation.tags=слайды,показ,офис,microsoft PDFToPresentation.tags=слайды,презентация,офис,microsoft
home.PDFToText.title=PDF в RTF (текст) home.PDFToText.title=PDF в RTF (текст)
home.PDFToText.desc=Преобразование PDF в текстовый или RTF формат home.PDFToText.desc=Преобразование PDF в текстовый или RTF формат
PDFToText.tags=richformat,richtextformat,rich text format PDFToText.tags=rtf,richformat,richtextformat,rich text format
home.PDFToHTML.title=PDF в HTML home.PDFToHTML.title=PDF в HTML
home.PDFToHTML.desc=Преобразование PDF в формат HTML home.PDFToHTML.desc=Преобразование PDF в формат HTML
PDFToHTML.tags=веб-контент,браузерный формат PDFToHTML.tags=html,веб-контент,браузерный формат
home.PDFToXML.title=PDF в XML home.PDFToXML.title=PDF в XML
@ -733,16 +733,16 @@ sanitizePdf.tags=очистка,безопасность,защита,удале
home.URLToPDF.title=URL/веб-сайт в PDF home.URLToPDF.title=URL/веб-сайт в PDF
home.URLToPDF.desc=Преобразует любой http(s)URL в PDF home.URLToPDF.desc=Преобразует любой http(s)URL в PDF
URLToPDF.tags=веб-захват,сохранение страницы,веб в док,архивация URLToPDF.tags=url,веб-захват,сохранение страницы,веб в док,архивация
home.HTMLToPDF.title=HTML в PDF home.HTMLToPDF.title=HTML в PDF
home.HTMLToPDF.desc=Преобразует любой HTML-файл или zip в PDF home.HTMLToPDF.desc=Преобразует любой HTML-файл или zip в PDF
HTMLToPDF.tags=разметка,веб-контент,преобразование,конвертация HTMLToPDF.tags=html,разметка,веб-контент,преобразование,конвертация
#eml-to-pdf #eml-to-pdf
home.EMLToPDF.title=Email в PDF home.EMLToPDF.title=Email в PDF
home.EMLToPDF.desc=Преобразует файлы электронной почты (EML) в формат PDF, включая заголовки, основную часть и встроенные изображения home.EMLToPDF.desc=Преобразует файлы электронной почты (EML) в формат PDF, включая заголовки, основную часть и встроенные изображения
EMLToPDF.tags=email,conversion,eml,message,transformation,convert,mail EMLToPDF.tags=eml,email,сообщение,преобразование,конвертация,почта,письмо
EMLToPDF.title=Email в PDF EMLToPDF.title=Email в PDF
EMLToPDF.header=Email в PDF EMLToPDF.header=Email в PDF
@ -752,17 +752,17 @@ EMLToPDF.downloadHtmlHelp=Это позволит вам просмотреть
EMLToPDF.includeAttachments=Включать вложения в формате PDF EMLToPDF.includeAttachments=Включать вложения в формате PDF
EMLToPDF.maxAttachmentSize=Максимальный размер вложения (MB) EMLToPDF.maxAttachmentSize=Максимальный размер вложения (MB)
EMLToPDF.help=Преобразует файлы электронной почты (EML) в формат PDF, включая заголовки, основную часть и встроенные изображения EMLToPDF.help=Преобразует файлы электронной почты (EML) в формат PDF, включая заголовки, основную часть и встроенные изображения
EMLToPDF.troubleshootingTip1=Электронная почта в формате HTML является более надежным процессом, поэтому при пакетной обработке рекомендуется сохранять оба EMLToPDF.troubleshootingTip1=Электронная почта в формате HTML является более надежным процессом, поэтому при пакетной обработке рекомендуется сохранять оба формата
EMLToPDF.troubleshootingTip2=При небольшом количестве электронных писем, если формат PDF искажен, вы можете загрузить HTML и переопределить часть проблемного HTML/CSS-кода. EMLToPDF.troubleshootingTip2=При небольшом количестве электронных писем, если формат PDF искажен, вы можете загрузить HTML и переопределить часть проблемного HTML/CSS-кода.
EMLToPDF.troubleshootingTip3=Embeddings, however, do not work with HTMLs EMLToPDF.troubleshootingTip3=Однако, встраивания не работают с HTMLs
home.MarkdownToPDF.title=Markdown в PDF home.MarkdownToPDF.title=Markdown в PDF
home.MarkdownToPDF.desc=Преобразует любой файл Markdown в PDF home.MarkdownToPDF.desc=Преобразует любой файл Markdown в PDF
MarkdownToPDF.tags=разметка,веб-контент,преобразование,конвертация MarkdownToPDF.tags=разметка,веб-контент,преобразование,конвертация,md
home.PDFToMarkdown.title=PDF to Markdown home.PDFToMarkdown.title=PDF to Markdown
home.PDFToMarkdown.desc=Преобразует любой PDF-файл в формат Markdown home.PDFToMarkdown.desc=Преобразует любой PDF-файл в формат Markdown
PDFToMarkdown.tags=markup,web-content,transformation,convert,md PDFToMarkdown.tags=разметка,веб-контент,преобразование,конвертацтя,md
home.getPdfInfo.title=Получить ВСЮ информацию о PDF home.getPdfInfo.title=Получить ВСЮ информацию о PDF
home.getPdfInfo.desc=Собирает всю возможную информацию о PDF home.getPdfInfo.desc=Собирает всю возможную информацию о PDF
@ -781,11 +781,11 @@ PdfToSinglePage.tags=одна страница
home.showJS.title=Показать Javascript home.showJS.title=Показать Javascript
home.showJS.desc=Ищет и отображает любой JS, внедрённый в PDF home.showJS.desc=Ищет и отображает любой JS, внедрённый в PDF
showJS.tags=JS showJS.tags=JS,Javascript
home.autoRedact.title=Автоматическое редактирование home.autoRedact.title=Автоматическое редактирование
home.autoRedact.desc=Автоматически закрашивает (чернит) текст в PDF на основе входного текста home.autoRedact.desc=Автоматически закрашивает (чернит) текст в PDF на основе входного текста
autoRedact.tags=Редактирование,Скрытие,зачернение,чёрный,маркер,скрытый autoRedact.tags=Редактирование,Скрытие,зачернение,чёрный,маркер,скрытый,автоматический
home.redact.title=Ручное редактирование home.redact.title=Ручное редактирование
home.redact.desc=Редактирует PDF на основе выбранного текста, нарисованных форм и/или выбранных страниц home.redact.desc=Редактирует PDF на основе выбранного текста, нарисованных форм и/или выбранных страниц
@ -798,7 +798,7 @@ tableExtraxt.tags=CSV,Извлечение таблиц,извлечение,к
home.autoSizeSplitPDF.title=Авторазделение по размеру/количеству home.autoSizeSplitPDF.title=Авторазделение по размеру/количеству
home.autoSizeSplitPDF.desc=Разделяет один PDF на несколько документов на основе размера, количества страниц или количества документов home.autoSizeSplitPDF.desc=Разделяет один PDF на несколько документов на основе размера, количества страниц или количества документов
autoSizeSplitPDF.tags=pdf,разделение,документ,организация autoSizeSplitPDF.tags=разделение,документ,организация
home.overlay-pdfs.title=Наложение PDF home.overlay-pdfs.title=Наложение PDF
@ -807,25 +807,25 @@ overlay-pdfs.tags=Наложение
home.split-by-sections.title=Разделить PDF по секциям home.split-by-sections.title=Разделить PDF по секциям
home.split-by-sections.desc=Разделяет каждую страницу PDF на меньшие горизонтальные и вертикальные секции home.split-by-sections.desc=Разделяет каждую страницу PDF на меньшие горизонтальные и вертикальные секции
split-by-sections.tags=Разделение по секциям,Разделить,Настроить split-by-sections.tags=Разделение по секциям,Разделить
home.AddStampRequest.title=Добавить штамп в PDF home.AddStampRequest.title=Добавить штамп в PDF
home.AddStampRequest.desc=Добавляет текстовые или графические штампы в указанных местах home.AddStampRequest.desc=Добавляет текстовые или графические штампы в указанных местах
AddStampRequest.tags=Штамп,Добавить изображение,центрировать изображение,Водяной знак,PDF,Встраивание,Настройка AddStampRequest.tags=Штамп,Добавить изображение,центрировать изображение,Водяной знак,Встраивание
home.removeImagePdf.title=Удалить изображение home.removeImagePdf.title=Удалить изображение
home.removeImagePdf.desc=Удаляет изображения из PDF для уменьшения размера файла home.removeImagePdf.desc=Удаляет изображения из PDF для уменьшения размера файла
removeImagePdf.tags=Удаление изображения,операции со страницами,Серверная часть removeImagePdf.tags=Удаление изображения,операции со страницами
home.splitPdfByChapters.title=Разделить PDF по главам home.splitPdfByChapters.title=Разделить PDF по главам
home.splitPdfByChapters.desc=Разделяет PDF на несколько файлов на основе структуры его глав home.splitPdfByChapters.desc=Разделяет PDF на несколько файлов на основе структуры его глав
splitPdfByChapters.tags=разделение,главы,закладки,организация splitPdfByChapters.tags=разделение,главы,закладки,оглавление
home.validateSignature.title=Проверка подписи PDF home.validateSignature.title=Проверка подписи PDF
home.validateSignature.desc=Проверка цифровых подписей и сертификатов в PDF-документах home.validateSignature.desc=Проверка цифровых подписей и сертификатов в PDF-документах
validateSignature.tags=подпись,проверка,валидация,pdf,сертификат,цифровая подпись,Проверка подписи,Проверка сертификата validateSignature.tags=подпись,проверка,валидация,сертификат,цифровая подпись,проверка подписи,проверка сертификата
#replace-invert-color #replace-invert-color
replace-color.title=Замена-Инверсия цвета replace-color.title=Замена-Инверсия цвета
@ -941,7 +941,7 @@ getPdfInfo.title=Получить информацию о PDF
getPdfInfo.header=Получить информацию о PDF getPdfInfo.header=Получить информацию о PDF
getPdfInfo.submit=Получить информацию getPdfInfo.submit=Получить информацию
getPdfInfo.downloadJson=Скачать JSON getPdfInfo.downloadJson=Скачать JSON
getPdfInfo.summary=PDF Summary getPdfInfo.summary=Краткое содержание PDF-файла
getPdfInfo.summary.encrypted=Этот PDF-файл зашифрован, поэтому с некоторыми приложениями могут возникнуть проблемы getPdfInfo.summary.encrypted=Этот PDF-файл зашифрован, поэтому с некоторыми приложениями могут возникнуть проблемы
getPdfInfo.summary.permissions=Этот PDF-файл имеет {0} ограниченные права доступа, которые могут ограничить то, что вы можете с ним делать getPdfInfo.summary.permissions=Этот PDF-файл имеет {0} ограниченные права доступа, которые могут ограничить то, что вы можете с ним делать
getPdfInfo.summary.compliance=Этот PDF-файл соответствует стандарту {0} getPdfInfo.summary.compliance=Этот PDF-файл соответствует стандарту {0}
@ -951,18 +951,18 @@ getPdfInfo.summary.encrypted.alert=Зашифрованный PDF-файл - э
getPdfInfo.summary.not.encrypted.alert=Незашифрованный PDF-файл - без защиты паролем getPdfInfo.summary.not.encrypted.alert=Незашифрованный PDF-файл - без защиты паролем
getPdfInfo.summary.permissions.alert=Права доступа ограничены - {0} действия запрещены getPdfInfo.summary.permissions.alert=Права доступа ограничены - {0} действия запрещены
getPdfInfo.summary.all.permissions.alert=Полные права доступа getPdfInfo.summary.all.permissions.alert=Полные права доступа
getPdfInfo.summary.compliance.alert={0} Compliant getPdfInfo.summary.compliance.alert={0} Совместимый
getPdfInfo.summary.no.compliance.alert=No Compliance Standards getPdfInfo.summary.no.compliance.alert=Не содержит стандартов соответствия
getPdfInfo.summary.security.section=Security Status getPdfInfo.summary.security.section=Уровень безопасности
getPdfInfo.section.BasicInfo=Basic Information about the PDF document including file size, page count, and language getPdfInfo.section.BasicInfo=Основная информация о документе PDF включает: размер файла, количество страниц и язык
getPdfInfo.section.Metadata=Document metadata including title, author, creation date and other document properties getPdfInfo.section.Metadata=Метаданные документа содержат: название, автора, дату создания и другие свойства документа
getPdfInfo.section.DocumentInfo=Technical details about the PDF document structure and version getPdfInfo.section.DocumentInfo=Технические подробности о структуре и версии PDF-документа
getPdfInfo.section.Compliancy=PDF standards compliance information (PDF/A, PDF/X, etc.) getPdfInfo.section.Compliancy=Информация о соответствии стандартам PDF (PDF/A, PDF/X, и т.д.)
getPdfInfo.section.Encryption=Security and encryption details of the document getPdfInfo.section.Encryption=Сведения о безопасности и шифровании документа
getPdfInfo.section.Permissions=Document permission settings that control what actions can be performed getPdfInfo.section.Permissions=Настройки разрешений документа, контролирующие, какие действия можно выполнять
getPdfInfo.section.Other=Additional document components like bookmarks, layers, and embedded files getPdfInfo.section.Other=Дополнительные компоненты документа, такие как закладки, слои и встроенные файлы
getPdfInfo.section.FormFields=Interactive form fields present in the document getPdfInfo.section.FormFields=Поля интерактивной формы, присутствующие в документе
getPdfInfo.section.PerPageInfo=Detailed information about each page in the document getPdfInfo.section.PerPageInfo=Подробная информация о каждой странице документа
#markdown-to-pdf #markdown-to-pdf
@ -970,7 +970,7 @@ MarkdownToPDF.title=Markdown в PDF
MarkdownToPDF.header=Markdown в PDF MarkdownToPDF.header=Markdown в PDF
MarkdownToPDF.submit=Преобразовать MarkdownToPDF.submit=Преобразовать
MarkdownToPDF.help=В разработке MarkdownToPDF.help=В разработке
MarkdownToPDF.credit=Использует WeasyPrint MarkdownToPDF.credit=Этот сервис использует WeasyPrint для преобразования файлов
#pdf-to-markdown #pdf-to-markdown
@ -983,7 +983,7 @@ PDFToMarkdown.submit=Преобразовать
URLToPDF.title=URL в PDF URLToPDF.title=URL в PDF
URLToPDF.header=URL в PDF URLToPDF.header=URL в PDF
URLToPDF.submit=Преобразовать URLToPDF.submit=Преобразовать
URLToPDF.credit=Использует WeasyPrint URLToPDF.credit=Этот сервис использует WeasyPrint для преобразования файлов
#html-to-pdf #html-to-pdf
@ -991,7 +991,7 @@ HTMLToPDF.title=HTML в PDF
HTMLToPDF.header=HTML в PDF HTMLToPDF.header=HTML в PDF
HTMLToPDF.help=Принимает HTML-файлы и ZIP-архивы, содержащие html/css/изображения и т.д. HTMLToPDF.help=Принимает HTML-файлы и ZIP-архивы, содержащие html/css/изображения и т.д.
HTMLToPDF.submit=Преобразовать HTMLToPDF.submit=Преобразовать
HTMLToPDF.credit=Использует WeasyPrint HTMLToPDF.credit=Этот сервис использует WeasyPrint для преобразования файлов
HTMLToPDF.zoom=Уровень масштабирования для отображения веб-сайта. HTMLToPDF.zoom=Уровень масштабирования для отображения веб-сайта.
HTMLToPDF.pageWidth=Ширина страницы в сантиметрах. (Пусто для значения по умолчанию) HTMLToPDF.pageWidth=Ширина страницы в сантиметрах. (Пусто для значения по умолчанию)
HTMLToPDF.pageHeight=Высота страницы в сантиметрах. (Пусто для значения по умолчанию) HTMLToPDF.pageHeight=Высота страницы в сантиметрах. (Пусто для значения по умолчанию)
@ -1047,7 +1047,7 @@ addPageNumbers.selectText.4=Начальный номер
addPageNumbers.selectText.5=Страницы для нумерации addPageNumbers.selectText.5=Страницы для нумерации
addPageNumbers.selectText.6=Пользовательский текст addPageNumbers.selectText.6=Пользовательский текст
addPageNumbers.customTextDesc=Пользовательский текст addPageNumbers.customTextDesc=Пользовательский текст
addPageNumbers.numberPagesDesc=Какие страницы нумеровать, по умолчанию 'все', также принимает 1-5 или 2,5,9 и т.д. addPageNumbers.numberPagesDesc=Какие страницы нумеровать, по умолчанию 'все', также принимаются значения 1-5 или 2,5,9 и т.д.
addPageNumbers.customNumberDesc=По умолчанию {n}, также принимает 'Страница {n} из {total}', 'Текст-{n}', '{filename}-{n}' addPageNumbers.customNumberDesc=По умолчанию {n}, также принимает 'Страница {n} из {total}', 'Текст-{n}', '{filename}-{n}'
addPageNumbers.submit=Добавить номера страниц addPageNumbers.submit=Добавить номера страниц
@ -1083,7 +1083,7 @@ autoSplitPDF.selectText.3=Загрузите один большой отска
autoSplitPDF.selectText.4=Разделительные страницы автоматически обнаруживаются и удаляются, гарантируя аккуратный конечный документ. autoSplitPDF.selectText.4=Разделительные страницы автоматически обнаруживаются и удаляются, гарантируя аккуратный конечный документ.
autoSplitPDF.formPrompt=Отправить PDF, содержащий разделители страниц Stirling-PDF: autoSplitPDF.formPrompt=Отправить PDF, содержащий разделители страниц Stirling-PDF:
autoSplitPDF.duplexMode=Двусторонний режим (сканирование с двух сторон) autoSplitPDF.duplexMode=Двусторонний режим (сканирование с двух сторон)
autoSplitPDF.dividerDownload2=Скачать 'Автоматический разделитель (с инструкциями).pdf' autoSplitPDF.dividerDownload2=Скачать 'Auto Splitter Divider (with instructions).pdf'
autoSplitPDF.submit=Отправить autoSplitPDF.submit=Отправить
@ -1253,7 +1253,7 @@ fileToPDF.submit=Преобразовать в PDF
compress.title=Сжать compress.title=Сжать
compress.header=Сжать PDF compress.header=Сжать PDF
compress.credit=Этот сервис использует qpdf для сжатия/оптимизации PDF. compress.credit=Этот сервис использует qpdf для сжатия/оптимизации PDF.
compress.grayscale.label=Применить шкалу серого для сжатия compress.grayscale.label=Примените оттенки серого для сжатия
compress.selectText.1=Параметры сжатия compress.selectText.1=Параметры сжатия
compress.selectText.1.1=1-3 сжатие PDF,</br> 4-6 лёгкое сжатие изображений,</br> 7-9 интенсивное сжатие изображений (значительно снижает качество изображений) compress.selectText.1.1=1-3 сжатие PDF,</br> 4-6 лёгкое сжатие изображений,</br> 7-9 интенсивное сжатие изображений (значительно снижает качество изображений)
compress.selectText.2=Уровень оптимизации: compress.selectText.2=Уровень оптимизации:
@ -1265,7 +1265,7 @@ compress.submit=Сжать
#Add image #Add image
addImage.title=Добавить изображение addImage.title=Добавить изображение
addImage.header=Добавить изображение в PDF addImage.header=Добавить изображение в PDF
addImage.everyPage=Каждая страница? addImage.everyPage=На каждую страницу?
addImage.upload=Добавить изображение addImage.upload=Добавить изображение
addImage.submit=Добавить изображение addImage.submit=Добавить изображение
@ -1341,7 +1341,7 @@ decrypt.serverError=Ошибка сервера при расшифровке: {
decrypt.success=Файл успешно расшифрован. decrypt.success=Файл успешно расшифрован.
#multiTool-advert #multiTool-advert
multiTool-advert.message=Эта функция также доступна на нашей <a href="{0}">странице мультиинструмента</a>. Попробуйте её для улучшенного постраничного интерфейса и дополнительных возможностей! multiTool-advert.message=Эта функция также доступна на нашей <a href="{0}">странице мультиинструментов</a>. Ознакомьтесь с расширенным постраничным интерфейсом и дополнительными функциями!
#view pdf #view pdf
viewPdf.title=Смотреть/Редактировать PDF viewPdf.title=Смотреть/Редактировать PDF
@ -1486,7 +1486,7 @@ changeMetadata.keywords=Ключевые слова:
changeMetadata.modDate=Дата изменения (yyyy/MM/dd HH:mm:ss): changeMetadata.modDate=Дата изменения (yyyy/MM/dd HH:mm:ss):
changeMetadata.producer=Производитель: changeMetadata.producer=Производитель:
changeMetadata.subject=Тема: changeMetadata.subject=Тема:
changeMetadata.trapped=Trapped: changeMetadata.trapped=Захвачен:
changeMetadata.selectText.4=Другие метаданные: changeMetadata.selectText.4=Другие метаданные:
changeMetadata.selectText.5=Добавить пользовательскую запись метаданных changeMetadata.selectText.5=Добавить пользовательскую запись метаданных
changeMetadata.submit=Изменить changeMetadata.submit=Изменить
@ -1614,15 +1614,15 @@ survey.please=Пожалуйста, примите участие в нашем
survey.disabled=(Всплывающее окно опроса будет отключено в следующих обновлениях, но будет доступно в нижней части страницы) survey.disabled=(Всплывающее окно опроса будет отключено в следующих обновлениях, но будет доступно в нижней части страницы)
survey.button=Пройти опрос survey.button=Пройти опрос
survey.dontShowAgain=Больше не показывать survey.dontShowAgain=Больше не показывать
survey.meeting.1=If you're using Stirling PDF at work, we'd love to speak to you. We're offering technical support sessions in exchange for a 15 minute user discovery session. survey.meeting.1=Если вы используете Stirling PDF в своей работе, мы будем рады с вами пообщаться. Мы предлагаем сеансы технической поддержки в обмен на 15-минутный сеанс знакомства с пользователями.
survey.meeting.2=This is a chance to: survey.meeting.2=Это возможность:
survey.meeting.3=Get help with deployment, integrations, or troubleshooting survey.meeting.3=Получить помощь в развертывании, интеграции или устранении неполадок
survey.meeting.4=Provide direct feedback on performance, edge cases, and feature gaps survey.meeting.4=Предоставляйте прямые отзывы о производительности, передовых решениях и недостатках в функциях
survey.meeting.5=Help us refine Stirling PDF for real-world enterprise use survey.meeting.5=Помогите нам усовершенствовать Stirling PDF для использования в реальных корпоративных условиях
survey.meeting.6=If you're interested, you can book time with our team directly. (English speaking only) survey.meeting.6=Если вы заинтересованы, вы можете забронировать время у нашей команды напрямую. (Только для говорящих по-английски)
survey.meeting.7=Looking forward to digging into your use cases and making Stirling PDF even better! survey.meeting.7=С нетерпением ждем возможности изучить ваши варианты использования и сделать Stirling PDF еще лучше!
survey.meeting.notInterested=Not a business and/or interested in a meeting? survey.meeting.notInterested=Не занимаетесь бизнесом и/или не заинтересованы во встрече?
survey.meeting.button=Book meeting survey.meeting.button=Забронировать встречу
#error #error
error.sorry=Извините за неполадки! error.sorry=Извините за неполадки!
@ -1664,7 +1664,7 @@ fileChooser.dragAndDropPDF=Перетащите PDF-файл
fileChooser.dragAndDropImage=Перетащите файл изображения fileChooser.dragAndDropImage=Перетащите файл изображения
fileChooser.hoveredDragAndDrop=Перетащите файл(ы) сюда fileChooser.hoveredDragAndDrop=Перетащите файл(ы) сюда
fileChooser.extractPDF=Извлечение... fileChooser.extractPDF=Извлечение...
fileChooser.addAttachments=drag & drop attachments here fileChooser.addAttachments=Перетащите вложения сюда
#release notes #release notes
releases.footer=Релизы releases.footer=Релизы
@ -1709,20 +1709,20 @@ validateSignature.cert.selfSigned=Самоподписанный
validateSignature.cert.bits=бит validateSignature.cert.bits=бит
# Audit Dashboard # Audit Dashboard
audit.dashboard.title=Audit Dashboard audit.dashboard.title=Панель аудита
audit.dashboard.systemStatus=Audit System Status audit.dashboard.systemStatus=Состояние системы аудита
audit.dashboard.status=Status audit.dashboard.status=Состояние
audit.dashboard.enabled=Enabled audit.dashboard.enabled=Включено
audit.dashboard.disabled=Disabled audit.dashboard.disabled=Выключено
audit.dashboard.currentLevel=Current Level audit.dashboard.currentLevel=Текущий уровень
audit.dashboard.retentionPeriod=Retention Period audit.dashboard.retentionPeriod=Срок хранения
audit.dashboard.days=days audit.dashboard.days=дней
audit.dashboard.totalEvents=Total Events audit.dashboard.totalEvents=Общее количество событий
# Audit Dashboard Tabs # Audit Dashboard Tabs
audit.dashboard.tab.dashboard=Dashboard audit.dashboard.tab.dashboard=Панель инструментов
audit.dashboard.tab.events=Audit Events audit.dashboard.tab.events=Аудит событий
audit.dashboard.tab.export=Export audit.dashboard.tab.export=Экспорт
# Dashboard Charts # Dashboard Charts
audit.dashboard.eventsByType=События по типу audit.dashboard.eventsByType=События по типу
audit.dashboard.eventsByUser=События по пользователю audit.dashboard.eventsByUser=События по пользователю
@ -1732,88 +1732,88 @@ audit.dashboard.period.30days=30 дней
audit.dashboard.period.90days=90 дней audit.dashboard.period.90days=90 дней
# Events Tab # Events Tab
audit.dashboard.auditEvents=Audit Events audit.dashboard.auditEvents=Аудит событий
audit.dashboard.filter.eventType=Event Type audit.dashboard.filter.eventType=Тип события
audit.dashboard.filter.allEventTypes=All event types audit.dashboard.filter.allEventTypes=Все типы событий
audit.dashboard.filter.user=User audit.dashboard.filter.user=Пользователь
audit.dashboard.filter.userPlaceholder=Filter by user audit.dashboard.filter.userPlaceholder=Фильтровать по пользователю
audit.dashboard.filter.startDate=Start Date audit.dashboard.filter.startDate=Дата начала
audit.dashboard.filter.endDate=End Date audit.dashboard.filter.endDate=Дата окончания
audit.dashboard.filter.apply=Apply Filters audit.dashboard.filter.apply=Применить фильтры
audit.dashboard.filter.reset=Reset Filters audit.dashboard.filter.reset=Сбросить фильтры
# Table Headers # Table Headers
audit.dashboard.table.id=ID audit.dashboard.table.id=ID
audit.dashboard.table.time=Time audit.dashboard.table.time=Время
audit.dashboard.table.user=User audit.dashboard.table.user=Пользователь
audit.dashboard.table.type=Type audit.dashboard.table.type=Тип
audit.dashboard.table.details=Details audit.dashboard.table.details=Подробности
audit.dashboard.table.viewDetails=View Details audit.dashboard.table.viewDetails=Просмотр подробной информации
# Pagination # Pagination
audit.dashboard.pagination.show=Show audit.dashboard.pagination.show=Показать
audit.dashboard.pagination.entries=entries audit.dashboard.pagination.entries=записи
audit.dashboard.pagination.pageInfo1=Page audit.dashboard.pagination.pageInfo1=Страница
audit.dashboard.pagination.pageInfo2=of audit.dashboard.pagination.pageInfo2=из
audit.dashboard.pagination.totalRecords=Total records: audit.dashboard.pagination.totalRecords=Всего записей:
# Modal # Modal
audit.dashboard.modal.eventDetails=Event Details audit.dashboard.modal.eventDetails=Подробности события
audit.dashboard.modal.id=ID audit.dashboard.modal.id=ID
audit.dashboard.modal.user=User audit.dashboard.modal.user=Пользователь
audit.dashboard.modal.type=Type audit.dashboard.modal.type=Тип
audit.dashboard.modal.time=Time audit.dashboard.modal.time=Время
audit.dashboard.modal.data=Data audit.dashboard.modal.data=Дата
# Export Tab # Export Tab
audit.dashboard.export.title=Export Audit Data audit.dashboard.export.title=Экспорт данных аудита
audit.dashboard.export.format=Export Format audit.dashboard.export.format=Формат экспорта
audit.dashboard.export.csv=CSV (Comma Separated Values) audit.dashboard.export.csv=CSV (Comma Separated Values)
audit.dashboard.export.json=JSON (JavaScript Object Notation) audit.dashboard.export.json=JSON (JavaScript Object Notation)
audit.dashboard.export.button=Export Data audit.dashboard.export.button=Экспортировать данные
audit.dashboard.export.infoTitle=Export Information audit.dashboard.export.infoTitle=Экспорт информации
audit.dashboard.export.infoDesc1=The export will include all audit events matching the selected filters. For large datasets, the export may take a few moments to generate. audit.dashboard.export.infoDesc1=Экспорт будет включать в себя все события аудита, соответствующие выбранным фильтрам. Для создания больших массивов данных экспорт может занять несколько минут.
audit.dashboard.export.infoDesc2=Exported data will include: audit.dashboard.export.infoDesc2=Экспортируемые данные будут включать:
audit.dashboard.export.infoItem1=Event ID audit.dashboard.export.infoItem1=Идентификатор события
audit.dashboard.export.infoItem2=User audit.dashboard.export.infoItem2=Пользователь
audit.dashboard.export.infoItem3=Event Type audit.dashboard.export.infoItem3=Тип события
audit.dashboard.export.infoItem4=Timestamp audit.dashboard.export.infoItem4=Время события
audit.dashboard.export.infoItem5=Event Data audit.dashboard.export.infoItem5=Данные события
# JavaScript i18n keys # JavaScript i18n keys
audit.dashboard.js.noEventsFound=No audit events found matching the current filters audit.dashboard.js.noEventsFound=События аудита, соответствующие текущим фильтрам, не найдены.
audit.dashboard.js.errorLoading=Error loading data: audit.dashboard.js.errorLoading=Ошибка загрузки данных:
audit.dashboard.js.errorRendering=Error rendering table: audit.dashboard.js.errorRendering=Ошибка рендеринга таблицы:
audit.dashboard.js.loadingPage=Loading page audit.dashboard.js.loadingPage=Загрузка страницы
#################### ####################
# Cookie banner # # Cookie banner #
#################### ####################
cookieBanner.popUp.title=How we use Cookies cookieBanner.popUp.title=Как мы используем файлы cookie
cookieBanner.popUp.description.1=We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you'll love. cookieBanner.popUp.description.1=Мы используем файлы cookie и другие технологии, чтобы улучшить работу Stirling PDF для вас, это помогает нам совершенствовать наши инструменты и создавать функции, которые вам понравятся.
cookieBanner.popUp.description.2=If youd rather not, clicking 'No Thanks' will only enable the essential cookies needed to keep things running smoothly. cookieBanner.popUp.description.2=Если вы предпочитаете этого не делать, нажав 'Нет, спасибо', вы активируете только основные файлы cookie, необходимые для бесперебойной работы.
cookieBanner.popUp.acceptAllBtn=Okay cookieBanner.popUp.acceptAllBtn=Хорошо
cookieBanner.popUp.acceptNecessaryBtn=No Thanks cookieBanner.popUp.acceptNecessaryBtn=Нет, спасибо
cookieBanner.popUp.showPreferencesBtn=Manage preferences cookieBanner.popUp.showPreferencesBtn=Управление предпочтениями
cookieBanner.preferencesModal.title=Consent Preferences Center cookieBanner.preferencesModal.title=Центр согласия предпочтений
cookieBanner.preferencesModal.acceptAllBtn=Accept all cookieBanner.preferencesModal.acceptAllBtn=Принять все
cookieBanner.preferencesModal.acceptNecessaryBtn=Reject all cookieBanner.preferencesModal.acceptNecessaryBtn=Отклонить все
cookieBanner.preferencesModal.savePreferencesBtn=Save preferences cookieBanner.preferencesModal.savePreferencesBtn=Сохранить предпочтения
cookieBanner.preferencesModal.closeIconLabel=Close modal cookieBanner.preferencesModal.closeIconLabel=Закрыть модальное окно
cookieBanner.preferencesModal.serviceCounterLabel=Service|Services cookieBanner.preferencesModal.serviceCounterLabel=Сервис|Услуги
cookieBanner.preferencesModal.subtitle=Cookie Usage cookieBanner.preferencesModal.subtitle=Использование файлов cookie
cookieBanner.preferencesModal.description.1=Stirling PDF uses cookies and similar technologies to enhance your experience and understand how our tools are used. This helps us improve performance, develop the features you care about, and provide ongoing support to our users. cookieBanner.preferencesModal.description.1=Stirling PDF использует файлы cookie и аналогичные технологии, чтобы улучшить ваш опыт и понять, как используются наши инструменты. Это помогает нам повышать производительность, разрабатывать функции, которые вам интересны, и обеспечивать постоянную поддержку наших пользователей.
cookieBanner.preferencesModal.description.2=Stirling PDF cannot—and will never—track or access the content of the documents you use. cookieBanner.preferencesModal.description.2=Stirling PDF не может — и никогда не будет — отслеживать или получать доступ к содержимому используемых вами документов.
cookieBanner.preferencesModal.description.3=Your privacy and trust are at the core of what we do. cookieBanner.preferencesModal.description.3=Ваша конфиденциальность и доверие лежат в основе того, что мы делаем.
cookieBanner.preferencesModal.necessary.title.1=Strictly Necessary Cookies cookieBanner.preferencesModal.necessary.title.1=Строго необходимые файлы cookie
cookieBanner.preferencesModal.necessary.title.2=Always Enabled cookieBanner.preferencesModal.necessary.title.2=Всегда включены
cookieBanner.preferencesModal.necessary.description=These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they cant be turned off. cookieBanner.preferencesModal.necessary.description=Эти файлы cookie необходимы для корректной работы веб-сайта. Они позволяют выполнять основные функции, такие как настройка параметров конфиденциальности, вход в систему и заполнение форм — именно поэтому их нельзя отключить.
cookieBanner.preferencesModal.analytics.title=Analytics cookieBanner.preferencesModal.analytics.title=Аналитика
cookieBanner.preferencesModal.analytics.description=These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with. cookieBanner.preferencesModal.analytics.description=Эти файлы cookie помогают нам понять, как используются наши инструменты, чтобы мы могли сосредоточиться на создании функций, которые больше всего ценятся нашим сообществом. Будьте уверены — Stirling PDF не может и никогда не будет отслеживать содержание документов, с которыми вы работаете.
#scannerEffect #scannerEffect
scannerEffect.title=Поддельное сканирование scannerEffect.title=Эффект сканирования
scannerEffect.header=Поддельное сканирование scannerEffect.header=Эффект сканирования
scannerEffect.description=Создайте PDF-файл, который выглядит так, как будто он был отсканирован scannerEffect.description=Создайте PDF-файл, который выглядит так, как будто он был отсканирован
scannerEffect.selectPDF=Выбрать PDF: scannerEffect.selectPDF=Выбрать PDF:
scannerEffect.quality=Качество сканирования scannerEffect.quality=Качество сканирования
@ -1825,12 +1825,12 @@ scannerEffect.rotation.none=Нет
scannerEffect.rotation.slight=Незначительный scannerEffect.rotation.slight=Незначительный
scannerEffect.rotation.moderate=Умеренный scannerEffect.rotation.moderate=Умеренный
scannerEffect.rotation.severe=Сильный scannerEffect.rotation.severe=Сильный
scannerEffect.submit=Создать поддельное сканирование scannerEffect.submit=Создать эффект сканирования
#home.scannerEffect #home.scannerEffect
home.scannerEffect.title=Поддельное сканирование home.scannerEffect.title=Эффект сканирования
home.scannerEffect.desc=Создайте PDF-файл, который выглядит так, как будто он был отсканирован home.scannerEffect.desc=Создайте PDF-файл, который выглядит так, как будто он был отсканирован
scannerEffect.tags=scan,simulate,realistic,convert scannerEffect.tags=scan,сканирование,симуляция,имитация,реалистично,преобразование
# ScannerEffect advanced settings (frontend) # ScannerEffect advanced settings (frontend)
scannerEffect.advancedSettings=Включите расширенные параметры сканирования scannerEffect.advancedSettings=Включите расширенные параметры сканирования
@ -1849,17 +1849,17 @@ scannerEffect.resolution=Разрешение (DPI)
# Table of Contents Feature # Table of Contents Feature
home.editTableOfContents.title=Редактировать оглавление home.editTableOfContents.title=Редактир оглавления
home.editTableOfContents.desc=Добавление или редактирование закладок и оглавления в PDF-документах home.editTableOfContents.desc=Добавление или редактирование закладок и оглавления в PDF-документах
editTableOfContents.tags=bookmarks,toc,navigation,index,table of contents,chapters,sections,outline editTableOfContents.tags=закладки, указатель, индекс, оглавление, главы, разделы, схема, навигация, структура
editTableOfContents.title=Редактировать оглавление editTableOfContents.title=Редактор оглавления
editTableOfContents.header=Добавление или редактирование закладок и оглавления в PDF-документах editTableOfContents.header=Добавление или редактирование закладок и оглавления в PDF-документах
editTableOfContents.replaceExisting=Replace existing bookmarks (uncheck to append to existing) editTableOfContents.replaceExisting=Заменить существующие закладки (снимите флажок, чтобы добавить к существующим)
editTableOfContents.editorTitle=Bookmark Editor editTableOfContents.editorTitle=Редактор закладок
editTableOfContents.editorDesc=Add and arrange bookmarks below. Click + to add child bookmarks. editTableOfContents.editorDesc=Добавьте и упорядочьте закладки ниже. Нажмите «+», чтобы добавить дочерние закладки.
editTableOfContents.addBookmark=Add New Bookmark editTableOfContents.addBookmark=Добавить новую закладку
editTableOfContents.desc.1=This tool allows you to add or edit the table of contents (bookmarks) in a PDF document. editTableOfContents.desc.1=Этот инструмент позволяет вам добавлять или редактировать оглавление (закладки) в PDF-документе.
editTableOfContents.desc.2=You can create a hierarchical structure by adding child bookmarks to parent bookmarks. editTableOfContents.desc.2=Вы можете создать иерархическую структуру, добавив дочерние закладки к родительским.
editTableOfContents.desc.3=Each bookmark requires a title and target page number. editTableOfContents.desc.3=Для каждой закладки требуется название и номер целевой страницы.
editTableOfContents.submit=Apply Table of Contents editTableOfContents.submit=Применить оглавление