Merge branch 'main' into 0.22.8Clone
This commit is contained in:
commit
b69646d00b
105 changed files with 4592 additions and 1252 deletions
28
.github/workflows/build.yml
vendored
28
.github/workflows/build.yml
vendored
|
@ -2,12 +2,12 @@ name: "Build repo"
|
|||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "**/*.md"
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
paths-ignore:
|
||||
- ".github/**"
|
||||
- "**/*.md"
|
||||
|
@ -25,16 +25,18 @@ jobs:
|
|||
fail-fast: false
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- uses: gradle/gradle-build-action@v2.4.2
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
arguments: build --no-build-cache
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build --no-build-cache
|
||||
|
|
15
.github/workflows/licenses-update.yml
vendored
15
.github/workflows/licenses-update.yml
vendored
|
@ -5,7 +5,7 @@ on:
|
|||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'build.gradle'
|
||||
- "build.gradle"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
@ -17,13 +17,15 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
java-version: "17"
|
||||
distribution: "adopt"
|
||||
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Run Gradle Command
|
||||
run: ./gradlew clean generateLicenseReport
|
||||
|
@ -44,7 +46,7 @@ jobs:
|
|||
|
||||
- name: Create Pull Request
|
||||
if: env.CHANGES_DETECTED == 'true'
|
||||
uses: peter-evans/create-pull-request@v3
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "Update 3rd Party Licenses"
|
||||
|
@ -58,4 +60,3 @@ jobs:
|
|||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
delete-branch: true
|
||||
|
||||
|
|
172
.github/workflows/push-docker.yml
vendored
172
.github/workflows/push-docker.yml
vendored
|
@ -14,105 +14,99 @@ jobs:
|
|||
push:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3.11.0
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
|
||||
- name: Run Gradle Command
|
||||
run: ./gradlew clean build
|
||||
env:
|
||||
DOCKER_ENABLE_SECURITY: false
|
||||
|
||||
- uses: gradle/gradle-build-action@v2.4.2
|
||||
env:
|
||||
DOCKER_ENABLE_SECURITY: false
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
arguments: clean build
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Make Gradle wrapper executable
|
||||
run: chmod +x gradlew
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_API }}
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v2.1.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ github.token }}
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Convert repository owner to lowercase
|
||||
id: repoowner
|
||||
run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')"
|
||||
- name: Convert repository owner to lowercase
|
||||
id: repoowner
|
||||
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Generate tags
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4.4.0
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||
tags: |
|
||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Generate tags
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||
tags: |
|
||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2.1.0
|
||||
- name: Build and push main Dockerfile
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2.5.0
|
||||
- name: Generate tags ultra-lite
|
||||
id: meta2
|
||||
uses: docker/metadata-action@v5
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||
tags: |
|
||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
|
||||
- name: Build and push main Dockerfile
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
with:
|
||||
context: .
|
||||
dockerfile: ./Dockerfile
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args:
|
||||
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
||||
|
||||
|
||||
- name: Generate tags ultra-lite
|
||||
id: meta2
|
||||
uses: docker/metadata-action@v4.4.0
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
|
||||
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/s-pdf
|
||||
tags: |
|
||||
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
|
||||
|
||||
|
||||
- name: Build and push Dockerfile-ultra-lite
|
||||
uses: docker/build-push-action@v4.0.0
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile-ultra-lite
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ steps.meta2.outputs.tags }}
|
||||
labels: ${{ steps.meta2.outputs.labels }}
|
||||
build-args:
|
||||
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
- name: Build and push Dockerfile-ultra-lite
|
||||
uses: docker/build-push-action@v5
|
||||
if: github.ref != 'refs/heads/main'
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile-ultra-lite
|
||||
push: true
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: ${{ steps.meta2.outputs.tags }}
|
||||
labels: ${{ steps.meta2.outputs.labels }}
|
||||
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
|
||||
platforms: linux/amd64,linux/arm64/v8
|
||||
|
|
84
.github/workflows/releaseArtifacts.yml
vendored
84
.github/workflows/releaseArtifacts.yml
vendored
|
@ -1,6 +1,7 @@
|
|||
name: Release Artifacts
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [created]
|
||||
permissions:
|
||||
|
@ -14,44 +15,61 @@ jobs:
|
|||
enable_security: [true, false]
|
||||
include:
|
||||
- enable_security: true
|
||||
file_suffix: '-with-login'
|
||||
file_suffix: "-with-login"
|
||||
- enable_security: false
|
||||
file_suffix: ''
|
||||
file_suffix: ""
|
||||
steps:
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3.11.0
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
with:
|
||||
gradle-version: 7.6
|
||||
|
||||
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
||||
run: ./gradlew clean createExe
|
||||
env:
|
||||
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
||||
- name: Generate jar (With Security=${{ matrix.enable_security }})
|
||||
run: ./gradlew clean createExe
|
||||
env:
|
||||
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }}
|
||||
|
||||
- name: Upload binaries to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ./build/launch4j/Stirling-PDF.exe
|
||||
asset_name: Stirling-PDF${{ matrix.file_suffix }}.exe
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
||||
- name: Rename binarie
|
||||
if: matrix.file_suffix != ''
|
||||
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
||||
|
||||
- name: Upload jar binaries to release
|
||||
uses: svenstaro/upload-release-action@v2
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
file: ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar
|
||||
asset_name: Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||
tag: ${{ github.ref }}
|
||||
overwrite: true
|
||||
- name: Upload Assets binarie
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
||||
name: Stirling-PDF${{ matrix.file_suffix }}.exe
|
||||
overwrite: true
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
- name: Upload binaries to release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
|
||||
|
||||
- name: Rename jar binaries
|
||||
run: cp ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||
|
||||
- name: Upload Assets jar binaries
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
path: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||
name: Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||
overwrite: true
|
||||
retention-days: 1
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Upload jar binaries to release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
|
||||
|
|
41
.github/workflows/swagger.yml
vendored
41
.github/workflows/swagger.yml
vendored
|
@ -8,31 +8,32 @@ on:
|
|||
|
||||
jobs:
|
||||
push:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: actions/checkout@v3.5.2
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "temurin"
|
||||
|
||||
- name: Set up JDK 17
|
||||
uses: actions/setup-java@v3.11.0
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'temurin'
|
||||
- uses: gradle/actions/setup-gradle@v3
|
||||
|
||||
- name: Grant execute permission for gradlew
|
||||
run: chmod +x gradlew
|
||||
- name: Generate Swagger documentation
|
||||
run: ./gradlew generateOpenApiDocs
|
||||
|
||||
- name: Generate Swagger documentation
|
||||
run: ./gradlew generateOpenApiDocs
|
||||
- name: Upload Swagger Documentation to SwaggerHub
|
||||
run: ./gradlew swaggerhubUpload
|
||||
env:
|
||||
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
|
||||
|
||||
- name: Upload Swagger Documentation to SwaggerHub
|
||||
run: ./gradlew swaggerhubUpload
|
||||
env:
|
||||
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set API version as published and default on SwaggerHub
|
||||
run: |
|
||||
curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}/settings/lifecycle" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"published\":true,\"default\":true}"
|
||||
env:
|
||||
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
|
||||
- name: Set API version as published and default on SwaggerHub
|
||||
run: |
|
||||
curl -X PUT -H "Authorization: ${SWAGGERHUB_API_KEY}" "https://api.swaggerhub.com/apis/Frooodle/Stirling-PDF/${{ steps.versionNumber.outputs.versionNumber }}/settings/lifecycle" -H "accept: application/json" -H "Content-Type: application/json" -d "{\"published\":true,\"default\":true}"
|
||||
env:
|
||||
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }}
|
||||
|
|
90
.github/workflows/sync_files.yml
vendored
Normal file
90
.github/workflows/sync_files.yml
vendored
Normal file
|
@ -0,0 +1,90 @@
|
|||
name: Sync Files
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "build.gradle"
|
||||
- "src/main/resources/messages_*.properties"
|
||||
- "scripts/translation_status.toml"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
sync-versions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install dependencies
|
||||
run: pip install pyyaml
|
||||
- name: Sync versions
|
||||
run: python .github/scripts/gradle_to_chart.py
|
||||
- name: Set up git config
|
||||
run: |
|
||||
git config --global user.email "GitHub Action <action@github.com>"
|
||||
git config --global user.name "GitHub Action <action@github.com>"
|
||||
- name: Run git add
|
||||
run: |
|
||||
git add .
|
||||
git diff --staged --quiet || git commit -m ":floppy_disk: Sync Versions
|
||||
> Made via sync_files.yml" || echo "no changes"
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Update files
|
||||
committer: GitHub Action <action@github.com>
|
||||
author: GitHub Action <action@github.com>
|
||||
signoff: true
|
||||
branch: sync_version
|
||||
title: ":floppy_disk: Update Version"
|
||||
body: |
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
delete-branch: true
|
||||
sync-readme:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.1.0
|
||||
with:
|
||||
python-version: "3.x"
|
||||
- name: Install dependencies
|
||||
run: pip install tomlkit
|
||||
- name: Sync README
|
||||
run: python scripts/counter_translation.py
|
||||
- name: Set up git config
|
||||
run: |
|
||||
git config --global user.email "GitHub Action <action@github.com>"
|
||||
git config --global user.name "GitHub Action <action@github.com>"
|
||||
- name: Run git add
|
||||
run: |
|
||||
git add .
|
||||
git diff --staged --quiet || git commit -m ":memo: Sync README
|
||||
> Made via sync_files.yml" || echo "no changes"
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Update files
|
||||
committer: GitHub Action <action@github.com>
|
||||
author: GitHub Action <action@github.com>
|
||||
signoff: true
|
||||
branch: sync_readme
|
||||
title: ":memo: Update README: Translation Progress Table"
|
||||
body: |
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
delete-branch: true
|
51
.github/workflows/sync_versions.yml
vendored
51
.github/workflows/sync_versions.yml
vendored
|
@ -1,51 +0,0 @@
|
|||
name: Sync Versions
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "build.gradle"
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
sync-versions:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4.1.1
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5.0.0
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- name: Install dependencies
|
||||
run: pip install pyyaml
|
||||
- name: Sync versions
|
||||
run: python .github/scripts/gradle_to_chart.py
|
||||
- name: Set up git config
|
||||
run: |
|
||||
git config --global user.email "GitHub Action <action@github.com>"
|
||||
git config --global user.name "GitHub Action <action@github.com>"
|
||||
- name: Run git add
|
||||
run: |
|
||||
git add .
|
||||
git diff --staged --quiet || git commit -m ":floppy_disk: Sync Versions
|
||||
> Made via sync_versions.yml" || echo "no changes"
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6.0.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: Update files
|
||||
committer: GitHub Action <action@github.com>
|
||||
author: GitHub Action <action@github.com>
|
||||
signoff: true
|
||||
branch: sync_version
|
||||
title: ":floppy_disk: Update Version"
|
||||
body: |
|
||||
Auto-generated by [create-pull-request][1]
|
||||
|
||||
[1]: https://github.com/peter-evans/create-pull-request
|
||||
draft: false
|
||||
delete-branch: true
|
64
.github/workflows/test.yml
vendored
64
.github/workflows/test.yml
vendored
|
@ -3,54 +3,36 @@ name: Docker Compose Tests
|
|||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- 'src/**'
|
||||
- '**.gradle'
|
||||
- '!src/main/java/resources/messages*'
|
||||
- 'exampleYmlFiles/**'
|
||||
- 'Dockerfile'
|
||||
- 'Dockerfile**'
|
||||
- "src/**"
|
||||
- "**.gradle"
|
||||
- "!src/main/java/resources/messages*"
|
||||
- "exampleYmlFiles/**"
|
||||
- "Dockerfile"
|
||||
- "Dockerfile**"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v2
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Java 17
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'adopt'
|
||||
- name: Set up Java 17
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "adopt"
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Run Docker Compose Tests
|
||||
run: |
|
||||
chmod +x ./gradlew
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.26.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
# sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
- name: Get version number
|
||||
id: versionNumber
|
||||
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)"
|
||||
|
||||
|
||||
- name: Cache Docker layers
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: /tmp/.buildx-cache
|
||||
key: ${{ runner.os }}-buildx-${{ steps.versionNumber.outputs.versionNumber }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-buildx-
|
||||
|
||||
- name: Install Docker Compose
|
||||
run: |
|
||||
sudo curl -L "https://github.com/docker/compose/releases/download/v2.6.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
sudo chmod +x /usr/local/bin/docker-compose
|
||||
|
||||
|
||||
- name: Run Docker Compose Tests
|
||||
run: |
|
||||
chmod +x ./test.sh
|
||||
./test.sh
|
||||
- name: Run Docker Compose Tests
|
||||
run: |
|
||||
chmod +x ./test.sh
|
||||
./test.sh
|
||||
|
|
|
@ -27,6 +27,10 @@ Please make sure your Pull Request adheres to the following guidelines:
|
|||
|
||||
If you would like to add or modify a translation, please see [How to add new languages to Stirling-PDF](HowToAddNewLanguage.md). Also, please create a Pull Request so others can use it!
|
||||
|
||||
## Docs
|
||||
|
||||
Documentation for Stirling-PDF is handled in a seperate repository. Please see [Docs repository](https://github.com/Stirling-Tools/Stirling-Tools.github.io) or use "edit this page"-button at the bottom of each page at [https://stirlingtools.com/docs/](https://stirlingtools.com/docs/).
|
||||
|
||||
## Fixing Bugs or Adding a New Feature
|
||||
|
||||
First, make sure you've read the section [Pull Requests](#pull-requests).
|
||||
|
|
11
Dockerfile
11
Dockerfile
|
@ -4,8 +4,8 @@ FROM alpine:20240329
|
|||
# Copy necessary files
|
||||
COPY scripts /scripts
|
||||
COPY pipeline /pipeline
|
||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto
|
||||
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto
|
||||
COPY src/main/resources/static/fonts/*.ttf /usr/share/fonts/opentype/noto/
|
||||
COPY src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
|
||||
COPY build/libs/*.jar app.jar
|
||||
|
||||
ARG VERSION_TAG
|
||||
|
@ -25,15 +25,17 @@ ENV DOCKER_ENABLE_SECURITY=false \
|
|||
RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/community" | tee -a /etc/apk/repositories && \
|
||||
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | tee -a /etc/apk/repositories && \
|
||||
apk update && \
|
||||
apk add --no-cache \
|
||||
ca-certificates \
|
||||
tzdata \
|
||||
tini \
|
||||
openssl \
|
||||
openssl-dev \
|
||||
bash \
|
||||
curl \
|
||||
openjdk17-jre \
|
||||
su-exec \
|
||||
font-noto-cjk \
|
||||
shadow \
|
||||
# Doc conversion
|
||||
libreoffice@testing \
|
||||
|
@ -58,7 +60,8 @@ RUN echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /et
|
|||
addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
|
||||
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
|
||||
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
|
||||
tesseract --list-langs
|
||||
tesseract --list-langs && \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
EXPOSE 8080
|
||||
|
||||
|
|
|
@ -34,5 +34,18 @@ Then simply translate all property entries within that file and make a PR into m
|
|||
|
||||
If you do not have a java IDE i am happy to verify the changes worked once you raise PR (but won't be able to verify the translations themselves)
|
||||
|
||||
## Handling Untranslatable Strings
|
||||
|
||||
Sometimes, certain strings in the properties file may not require translation because they are the same in the target language or are universal (like names of protocols, certain terminologies, etc.). To ensure accurate statistics for language progress, these strings should be added to the `ignore_translation.toml` file located in the `scripts` directory. This will exclude them from the translation progress calculations.
|
||||
|
||||
For example, if the English string error=Error does not need translation in Polish, add it to the ignore_translation.toml under the Polish section:
|
||||
|
||||
```toml
|
||||
[pl_PL]
|
||||
ignore = [
|
||||
"language.direction", # Existing entries
|
||||
"error" # Add new entries here
|
||||
]
|
||||
```
|
||||
|
||||
Make sure to place the entry under the correct language section. This helps maintain the accuracy of translation progress statistics and ensures that the translation tool or scripts do not misinterpret the completion rate.
|
||||
|
|
60
README.md
60
README.md
|
@ -10,7 +10,7 @@
|
|||
|
||||
[![Deploy to DO](https://www.deploytodo.com/do-btn-blue.svg)](https://cloud.digitalocean.com/apps/new?repo=https://github.com/Stirling-Tools/Stirling-PDF/tree/digitalOcean&refcode=c3210994b1af)
|
||||
|
||||
This is a robust, locally hosted web-based PDF manipulation tool using Docker. It enables you to carry out various operations on PDF files, including splitting, merging, converting, reorganizing, adding images, rotating, compressing, and more. Originally developed entirely by ChatGPT, this locally hosted web application has evolved to encompass a comprehensive set of features, addressing all your PDF requirements.
|
||||
This is a robust, locally hosted web-based PDF manipulation tool using Docker. It enables you to carry out various operations on PDF files, including splitting, merging, converting, reorganizing, adding images, rotating, compressing, and more. This locally hosted web application has evolved to encompass a comprehensive set of features, addressing all your PDF requirements.
|
||||
|
||||
Stirling PDF does not initiate any outbound calls for record-keeping or tracking purposes.
|
||||
|
||||
|
@ -121,6 +121,7 @@ docker run -d \
|
|||
-v /location/of/logs:/logs \
|
||||
-e DOCKER_ENABLE_SECURITY=false \
|
||||
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
|
||||
-e LANGS=en_GB \
|
||||
--name stirling-pdf \
|
||||
frooodle/s-pdf:latest
|
||||
|
||||
|
@ -147,6 +148,7 @@ services:
|
|||
environment:
|
||||
- DOCKER_ENABLE_SECURITY=false
|
||||
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
|
||||
- LANGS=en_GB
|
||||
```
|
||||
|
||||
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
|
||||
|
@ -157,36 +159,37 @@ Please view https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToUseOCR
|
|||
|
||||
## Supported Languages
|
||||
|
||||
Stirling PDF currently supports 26!
|
||||
Stirling PDF currently supports 27!
|
||||
|
||||
| Language | Progress |
|
||||
| ------------------------------------------- | -------------------------------------- |
|
||||
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
|
||||
| English (US) (en_US) | ![100%](https://geps.dev/progress/100) |
|
||||
| Arabic (العربية) (ar_AR) | ![58%](https://geps.dev/progress/58) |
|
||||
| German (Deutsch) (de_DE) | ![95%](https://geps.dev/progress/95) |
|
||||
| French (Français) (fr_FR) | ![99%](https://geps.dev/progress/99) |
|
||||
| Spanish (Español) (es_ES) | ![95%](https://geps.dev/progress/95) |
|
||||
| Simplified Chinese (简体中文) (zh_CN) | ![99%](https://geps.dev/progress/99) |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) | ![97%](https://geps.dev/progress/97) |
|
||||
| Catalan (Català) (ca_CA) | ![65%](https://geps.dev/progress/65) |
|
||||
| Arabic (العربية) (ar_AR) | ![42%](https://geps.dev/progress/42) |
|
||||
| German (Deutsch) (de_DE) | ![100%](https://geps.dev/progress/100) |
|
||||
| French (Français) (fr_FR) | ![91%](https://geps.dev/progress/91) |
|
||||
| Spanish (Español) (es_ES) | ![99%](https://geps.dev/progress/99) |
|
||||
| Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) |
|
||||
| Traditional Chinese (繁體中文) (zh_TW) | ![98%](https://geps.dev/progress/98) |
|
||||
| Catalan (Català) (ca_CA) | ![51%](https://geps.dev/progress/51) |
|
||||
| Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
|
||||
| Swedish (Svenska) (sv_SE) | ![58%](https://geps.dev/progress/58) |
|
||||
| Polish (Polski) (pl_PL) | ![60%](https://geps.dev/progress/60) |
|
||||
| Romanian (Română) (ro_RO) | ![58%](https://geps.dev/progress/58) |
|
||||
| Korean (한국어) (ko_KR) | ![92%](https://geps.dev/progress/92) |
|
||||
| Portuguese Brazilian (Português) (pt_BR) | ![74%](https://geps.dev/progress/74) |
|
||||
| Russian (Русский) (ru_RU) | ![92%](https://geps.dev/progress/92) |
|
||||
| Basque (Euskara) (eu_ES) | ![76%](https://geps.dev/progress/76) |
|
||||
| Japanese (日本語) (ja_JP) | ![92%](https://geps.dev/progress/92) |
|
||||
| Dutch (Nederlands) (nl_NL) | ![92%](https://geps.dev/progress/92) |
|
||||
| Greek (Ελληνικά) (el_GR) | ![77%](https://geps.dev/progress/77) |
|
||||
| Swedish (Svenska) (sv_SE) | ![42%](https://geps.dev/progress/42) |
|
||||
| Polish (Polski) (pl_PL) | ![44%](https://geps.dev/progress/44) |
|
||||
| Romanian (Română) (ro_RO) | ![41%](https://geps.dev/progress/41) |
|
||||
| Korean (한국어) (ko_KR) | ![91%](https://geps.dev/progress/91) |
|
||||
| Portuguese Brazilian (Português) (pt_BR) | ![64%](https://geps.dev/progress/64) |
|
||||
| Russian (Русский) (ru_RU) | ![91%](https://geps.dev/progress/91) |
|
||||
| Basque (Euskara) (eu_ES) | ![66%](https://geps.dev/progress/66) |
|
||||
| Japanese (日本語) (ja_JP) | ![91%](https://geps.dev/progress/91) |
|
||||
| Dutch (Nederlands) (nl_NL) | ![88%](https://geps.dev/progress/88) |
|
||||
| Greek (Ελληνικά) (el_GR) | ![88%](https://geps.dev/progress/88) |
|
||||
| Turkish (Türkçe) (tr_TR) | ![99%](https://geps.dev/progress/99) |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) | ![87%](https://geps.dev/progress/87) |
|
||||
| Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) |
|
||||
| Hungarian (Magyar) (hu_HU) | ![87%](https://geps.dev/progress/87) |
|
||||
| Bulgarian (Български) (bg_BG) | ![82%](https://geps.dev/progress/82) |
|
||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![89%](https://geps.dev/progress/89) |
|
||||
| Indonesia (Bahasa Indonesia) (id_ID) | ![82%](https://geps.dev/progress/82) |
|
||||
| Hindi (हिंदी) (hi_IN) | ![82%](https://geps.dev/progress/82) |
|
||||
| Hungarian (Magyar) (hu_HU) | ![81%](https://geps.dev/progress/81) |
|
||||
| Bulgarian (Български) (bg_BG) | ![75%](https://geps.dev/progress/75) |
|
||||
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![84%](https://geps.dev/progress/84) |
|
||||
| Ukrainian (Українська) (uk_UA) | ![90%](https://geps.dev/progress/90) |
|
||||
|
||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||
|
||||
|
@ -198,7 +201,7 @@ Stirling PDF allows easy customization of the app.
|
|||
Includes things like
|
||||
|
||||
- Custom application name
|
||||
- Custom slogans, icons, images, and even custom HTML (via file overrides)
|
||||
- Custom slogans, icons, HTML, images CSS etc (via file overrides)
|
||||
|
||||
There are two options for this, either using the generated settings file ``settings.yml``
|
||||
This file is located in the ``/configs`` directory and follows standard YAML formatting
|
||||
|
@ -224,6 +227,9 @@ system:
|
|||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||
customStaticFilePath: '/customFiles/static/' # Directory path for custom static files
|
||||
showUpdate: true # see when a new update is available
|
||||
showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||
customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template html files
|
||||
|
||||
#ui:
|
||||
# appName: exampleAppName # Application's visible name
|
||||
|
@ -238,6 +244,8 @@ metrics:
|
|||
enabled: true # 'true' to enable Info APIs endpoints (view http://localhost:8080/swagger-ui/index.html#/API to learn more), 'false' to disable
|
||||
```
|
||||
|
||||
There is an additional config file ``/configs/custom_settings.yml`` were users familiar with java and spring application.properties can input their own settings on-top of Stirling-PDFs existing ones
|
||||
|
||||
### Extra notes
|
||||
|
||||
- Endpoints. Currently, the endpoints ENDPOINTS_TO_REMOVE and GROUPS_TO_REMOVE can include comma separate lists of endpoints and groups to disable as example ENDPOINTS_TO_REMOVE=img-to-pdf,remove-pages would disable both image-to-pdf and remove pages, GROUPS_TO_REMOVE=LibreOffice Would disable all things that use LibreOffice. You can see a list of all endpoints and groups [here](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/Endpoint-groups.md)
|
||||
|
@ -249,13 +257,13 @@ metrics:
|
|||
- ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values
|
||||
- ``DOCKER_ENABLE_SECURITY`` to tell docker to download security jar (required as true for auth login)
|
||||
- ``INSTALL_BOOK_AND_ADVANCED_HTML_OPS`` to download calibre onto stirling-pdf enabling pdf to/from book and advanced html conversion
|
||||
- ``LANGS`` to define custom font libraries to install for use for document conversions
|
||||
|
||||
## API
|
||||
|
||||
For those wanting to use Stirling-PDFs backend API to link with their own custom scripting to edit PDFs you can view all existing API documentation
|
||||
[here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/) or navigate to /swagger-ui/index.html of your stirling-pdf instance for your versions documentation (Or by following the API button in your settings of Stirling-PDF)
|
||||
|
||||
|
||||
## Login authentication
|
||||
|
||||
![stirling-login](images/login-light.png)
|
||||
|
|
|
@ -12,7 +12,7 @@ plugins {
|
|||
import com.github.jk1.license.render.*
|
||||
|
||||
group = 'stirling.software'
|
||||
version = '0.22.8'
|
||||
version = '0.23.1'
|
||||
sourceCompatibility = '17'
|
||||
|
||||
repositories {
|
||||
|
@ -99,6 +99,7 @@ dependencies {
|
|||
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.4'
|
||||
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE'
|
||||
implementation "org.springframework.boot:spring-boot-starter-data-jpa:3.2.4"
|
||||
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client:3.2.4'
|
||||
|
||||
//2.2.x requires rebuild of DB file.. need migration path
|
||||
implementation "com.h2database:h2:2.1.214"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
apiVersion: v2
|
||||
appVersion: 0.22.8
|
||||
appVersion: 0.23.1
|
||||
description: locally hosted web application that allows you to perform various operations
|
||||
on PDF files
|
||||
home: https://github.com/Stirling-Tools/Stirling-PDF
|
||||
|
|
39
exampleYmlFiles/docker-compose-latest-security-with-sso.yml
Normal file
39
exampleYmlFiles/docker-compose-latest-security-with-sso.yml
Normal file
|
@ -0,0 +1,39 @@
|
|||
version: '3.3'
|
||||
services:
|
||||
stirling-pdf:
|
||||
container_name: Stirling-PDF-Security
|
||||
image: frooodle/s-pdf:latest
|
||||
deploy:
|
||||
resources:
|
||||
limits:
|
||||
memory: 4G
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "curl -f http://localhost:8080/api/v1/info/status | grep -q 'UP' && curl -fL http://localhost:8080/ | grep -q 'Please sign in'"]
|
||||
interval: 5s
|
||||
timeout: 10s
|
||||
retries: 16
|
||||
ports:
|
||||
- 8080:8080
|
||||
volumes:
|
||||
- /stirling/latest/data:/usr/share/tessdata:rw
|
||||
- /stirling/latest/config:/configs:rw
|
||||
- /stirling/latest/logs:/logs:rw
|
||||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "true"
|
||||
SECURITY_ENABLELOGIN: "true"
|
||||
SECURITY_OAUTH2_ENABLED: "true"
|
||||
SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Striling-PDF
|
||||
SECURITY_OAUTH2_ISSUER: "https://accounts.google.com" # Change with any other provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||
SECURITY_OAUTH2_CLIENTID: "<YOUR CLIENT ID>.apps.googleusercontent.com" # Client ID from your provider
|
||||
SECURITY_OAUTH2_CLIENTSECRET: "<YOUR CLIENT SECRET>" # Client Secret from your provider
|
||||
PUID: 1002
|
||||
PGID: 1002
|
||||
UMASK: "022"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest with Security
|
||||
UI_APPNAMENAVBAR: Stirling-PDF Latest
|
||||
SYSTEM_MAXFILESIZE: "100"
|
||||
METRICS_ENABLED: "true"
|
||||
SYSTEM_GOOGLEVISIBILITY: "true"
|
||||
restart: on-failure:5
|
|
@ -21,6 +21,8 @@ services:
|
|||
environment:
|
||||
DOCKER_ENABLE_SECURITY: "false"
|
||||
SECURITY_ENABLELOGIN: "false"
|
||||
LANGS: "en_GB,en_US,ar_AR,de_DE,fr_FR,es_ES,zh_CN,zh_TW,ca_CA,it_IT,sv_SE,pl_PL,ro_RO,ko_KR,pt_BR,ru_RU,el_GR,hi_IN,hu_HU,tr_TR,id_ID"
|
||||
INSTALL_BOOK_AND_ADVANCED_HTML_OPS: "true"
|
||||
SYSTEM_DEFAULTLOCALE: en-US
|
||||
UI_APPNAME: Stirling-PDF
|
||||
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest
|
||||
|
|
|
@ -10,49 +10,77 @@ Author: Ludy87
|
|||
Example:
|
||||
To use this script, simply run it from command line:
|
||||
$ python counter_translation.py
|
||||
"""
|
||||
import os
|
||||
""" # noqa: D205
|
||||
|
||||
import glob
|
||||
import os
|
||||
import re
|
||||
from typing import List, Tuple
|
||||
|
||||
import tomlkit
|
||||
import tomlkit.toml_file
|
||||
|
||||
|
||||
def write_readme(progress_list: List[Tuple[str, int]]) -> None:
|
||||
"""
|
||||
Updates the progress status in the README.md file based
|
||||
def convert_to_multiline(data: tomlkit.TOMLDocument) -> tomlkit.TOMLDocument:
|
||||
"""Converts 'ignore' and 'missing' arrays to multiline arrays and sorts the first-level keys of the TOML document.
|
||||
Enhances readability and consistency in the TOML file by ensuring arrays contain unique and sorted entries.
|
||||
|
||||
Parameters:
|
||||
data (tomlkit.TOMLDocument): The original TOML document containing the data.
|
||||
|
||||
Returns:
|
||||
tomlkit.TOMLDocument: A new TOML document with sorted keys and properly formatted arrays.
|
||||
""" # noqa: D205
|
||||
sorted_data = tomlkit.document()
|
||||
for key in sorted(data.keys()):
|
||||
value = data[key]
|
||||
if isinstance(value, dict):
|
||||
new_table = tomlkit.table()
|
||||
for subkey in ("ignore", "missing"):
|
||||
if subkey in value:
|
||||
# Convert the list to a set to remove duplicates, sort it, and convert to multiline for readability
|
||||
unique_sorted_array = sorted(set(value[subkey]))
|
||||
array = tomlkit.array()
|
||||
array.multiline(True)
|
||||
for item in unique_sorted_array:
|
||||
array.append(item)
|
||||
new_table[subkey] = array
|
||||
sorted_data[key] = new_table
|
||||
else:
|
||||
# Add other types of data unchanged
|
||||
sorted_data[key] = value
|
||||
return sorted_data
|
||||
|
||||
|
||||
def write_readme(progress_list: list[tuple[str, int]]) -> None:
|
||||
"""Updates the progress status in the README.md file based
|
||||
on the provided progress list.
|
||||
|
||||
Parameters:
|
||||
progress_list (List[Tuple[str, int]]): A list of tuples containing
|
||||
progress_list (list[tuple[str, int]]): A list of tuples containing
|
||||
language and progress percentage.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
with open("README.md", "r", encoding="utf-8") as file:
|
||||
content = file.read()
|
||||
""" # noqa: D205
|
||||
with open("README.md", encoding="utf-8") as file:
|
||||
content = file.readlines()
|
||||
|
||||
lines = content.split("\n")
|
||||
for i, line in enumerate(lines[2:], start=2):
|
||||
for i, line in enumerate(content[2:], start=2):
|
||||
for progress in progress_list:
|
||||
language, value = progress
|
||||
if language in line:
|
||||
match = re.search(r"\!\[(\d+(\.\d+)?)%\]\(.*\)", line)
|
||||
if match:
|
||||
lines[i] = line.replace(
|
||||
if match := re.search(r"\!\[(\d+(\.\d+)?)%\]\(.*\)", line):
|
||||
content[i] = line.replace(
|
||||
match.group(0),
|
||||
f"![{value}%](https://geps.dev/progress/{value})",
|
||||
)
|
||||
|
||||
new_content = "\n".join(lines)
|
||||
|
||||
with open("README.md", "w", encoding="utf-8") as file:
|
||||
file.write(new_content)
|
||||
file.writelines(content)
|
||||
|
||||
|
||||
def compare_files(default_file_path, files_directory) -> List[Tuple[str, int]]:
|
||||
"""
|
||||
Compares the default properties file with other
|
||||
def compare_files(default_file_path, file_paths, translation_status_file) -> list[tuple[str, int]]:
|
||||
"""Compares the default properties file with other
|
||||
properties files in the directory.
|
||||
|
||||
Parameters:
|
||||
|
@ -60,20 +88,22 @@ def compare_files(default_file_path, files_directory) -> List[Tuple[str, int]]:
|
|||
files_directory (str): The directory containing other properties files.
|
||||
|
||||
Returns:
|
||||
List[Tuple[str, int]]: A list of tuples containing
|
||||
list[tuple[str, int]]: A list of tuples containing
|
||||
language and progress percentage.
|
||||
"""
|
||||
file_paths = glob.glob(os.path.join(files_directory, "messages_*.properties"))
|
||||
num_lines = sum(1 for _ in open(default_file_path, encoding="utf-8"))
|
||||
""" # noqa: D205
|
||||
num_lines = sum(
|
||||
1 for line in open(default_file_path, encoding="utf-8") if line.strip() and not line.strip().startswith("#")
|
||||
)
|
||||
|
||||
result_list = []
|
||||
sort_translation_status: tomlkit.TOMLDocument
|
||||
|
||||
# read toml
|
||||
with open(translation_status_file, encoding="utf-8") as f:
|
||||
sort_translation_status = tomlkit.parse(f.read())
|
||||
|
||||
for file_path in file_paths:
|
||||
language = (
|
||||
os.path.basename(file_path)
|
||||
.split("messages_", 1)[1]
|
||||
.split(".properties", 1)[0]
|
||||
)
|
||||
language = os.path.basename(file_path).split("messages_", 1)[1].split(".properties", 1)[0]
|
||||
|
||||
fails = 0
|
||||
if "en_GB" in language or "en_US" in language:
|
||||
|
@ -81,9 +111,21 @@ def compare_files(default_file_path, files_directory) -> List[Tuple[str, int]]:
|
|||
result_list.append(("en_US", 100))
|
||||
continue
|
||||
|
||||
with open(default_file_path, "r", encoding="utf-8") as default_file, open(
|
||||
file_path, "r", encoding="utf-8"
|
||||
) as file:
|
||||
if language not in sort_translation_status:
|
||||
sort_translation_status[language] = tomlkit.table()
|
||||
|
||||
if (
|
||||
"ignore" not in sort_translation_status[language]
|
||||
or len(sort_translation_status[language].get("ignore", [])) < 1
|
||||
):
|
||||
sort_translation_status[language]["ignore"] = tomlkit.array(["language.direction"])
|
||||
|
||||
# if "missing" not in sort_translation_status[language]:
|
||||
# sort_translation_status[language]["missing"] = tomlkit.array()
|
||||
# elif "language.direction" in sort_translation_status[language]["missing"]:
|
||||
# sort_translation_status[language]["missing"].remove("language.direction")
|
||||
|
||||
with open(default_file_path, encoding="utf-8") as default_file, open(file_path, encoding="utf-8") as file:
|
||||
for _ in range(5):
|
||||
next(default_file)
|
||||
try:
|
||||
|
@ -91,24 +133,47 @@ def compare_files(default_file_path, files_directory) -> List[Tuple[str, int]]:
|
|||
except StopIteration:
|
||||
fails = num_lines
|
||||
|
||||
for _, (line_default, line_file) in enumerate(
|
||||
zip(default_file, file), start=6
|
||||
):
|
||||
for line_num, (line_default, line_file) in enumerate(zip(default_file, file), start=6):
|
||||
try:
|
||||
# Ignoring empty lines and lines start with #
|
||||
if line_default.strip() == "" or line_default.startswith("#"):
|
||||
continue
|
||||
|
||||
default_key, default_value = line_default.split("=", 1)
|
||||
file_key, file_value = line_file.split("=", 1)
|
||||
if (
|
||||
line_default.split("=", 1)[1].strip()
|
||||
== line_file.split("=", 1)[1].strip()
|
||||
default_value.strip() == file_value.strip()
|
||||
and default_key.strip() not in sort_translation_status[language]["ignore"]
|
||||
):
|
||||
print(f"{language}: Line {line_num} is missing the translation.")
|
||||
# if default_key.strip() not in sort_translation_status[language]["missing"]:
|
||||
# missing_array = tomlkit.array()
|
||||
# missing_array.append(default_key.strip())
|
||||
# missing_array.multiline(True)
|
||||
# sort_translation_status[language]["missing"].extend(missing_array)
|
||||
fails += 1
|
||||
# elif default_key.strip() in sort_translation_status[language]["ignore"]:
|
||||
# if default_key.strip() in sort_translation_status[language]["missing"]:
|
||||
# sort_translation_status[language]["missing"].remove(default_key.strip())
|
||||
if default_value.strip() != file_value.strip():
|
||||
# if default_key.strip() in sort_translation_status[language]["missing"]:
|
||||
# sort_translation_status[language]["missing"].remove(default_key.strip())
|
||||
if default_key.strip() in sort_translation_status[language]["ignore"]:
|
||||
sort_translation_status[language]["ignore"].remove(default_key.strip())
|
||||
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
print(f"{language}: {fails} out of {num_lines} lines are not translated.")
|
||||
result_list.append(
|
||||
(
|
||||
language,
|
||||
int((num_lines - fails) * 100 / num_lines),
|
||||
)
|
||||
)
|
||||
translation_status = convert_to_multiline(sort_translation_status)
|
||||
with open(translation_status_file, "w", encoding="utf-8") as file:
|
||||
file.write(tomlkit.dumps(translation_status))
|
||||
|
||||
unique_data = list(set(result_list))
|
||||
unique_data.sort(key=lambda x: x[1], reverse=True)
|
||||
|
@ -118,5 +183,10 @@ def compare_files(default_file_path, files_directory) -> List[Tuple[str, int]]:
|
|||
|
||||
if __name__ == "__main__":
|
||||
directory = os.path.join(os.getcwd(), "src", "main", "resources")
|
||||
messages_file_paths = glob.glob(os.path.join(directory, "messages_*.properties"))
|
||||
reference_file = os.path.join(directory, "messages_en_GB.properties")
|
||||
write_readme(compare_files(reference_file, directory))
|
||||
|
||||
scripts_directory = os.path.join(os.getcwd(), "scripts")
|
||||
translation_state_file = os.path.join(scripts_directory, "translation_status.toml")
|
||||
|
||||
write_readme(compare_files(reference_file, messages_file_paths, translation_state_file))
|
||||
|
|
|
@ -1,26 +1,31 @@
|
|||
#!/bin/sh
|
||||
#!/bin/bash
|
||||
|
||||
# Update the user and group IDs as per environment variables
|
||||
if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then
|
||||
usermod -o -u "$PUID" stirlingpdfuser || true
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then
|
||||
groupmod -o -g "$PGID" stirlingpdfgroup || true
|
||||
fi
|
||||
umask "$UMASK" || true
|
||||
|
||||
|
||||
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then
|
||||
apk add --no-cache calibre@testing
|
||||
fi
|
||||
|
||||
/scripts/download-security-jar.sh
|
||||
|
||||
if [[ -n "$LANGS" ]]; then
|
||||
/scripts/installFonts.sh $LANGS
|
||||
fi
|
||||
|
||||
echo "Setting permissions and ownership for necessary directories..."
|
||||
if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar; then
|
||||
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar || true
|
||||
# If chown succeeds, execute the command as stirlingpdfuser
|
||||
# Attempt to change ownership of directories and files
|
||||
if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /app.jar; then
|
||||
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline /app.jar || true
|
||||
# If chown succeeds, execute the command as stirlingpdfuser
|
||||
exec su-exec stirlingpdfuser "$@"
|
||||
else
|
||||
# If chown fails, execute the command without changing the user context
|
||||
|
|
|
@ -13,18 +13,6 @@ if [ -d /usr/share/tesseract-ocr/5/tessdata ]; then
|
|||
cp -r /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata || true;
|
||||
fi
|
||||
|
||||
# Update the user and group IDs as per environment variables
|
||||
if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then
|
||||
usermod -o -u "$PUID" stirlingpdfuser || true
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then
|
||||
groupmod -o -g "$PGID" stirlingpdfgroup || true
|
||||
fi
|
||||
umask "$UMASK" || true
|
||||
|
||||
|
||||
# Check if TESSERACT_LANGS environment variable is set and is not empty
|
||||
if [[ -n "$TESSERACT_LANGS" ]]; then
|
||||
# Convert comma-separated values to a space-separated list
|
||||
|
@ -40,20 +28,4 @@ if [[ -n "$TESSERACT_LANGS" ]]; then
|
|||
done
|
||||
fi
|
||||
|
||||
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then
|
||||
apk add --no-cache calibre@testing
|
||||
fi
|
||||
|
||||
/scripts/download-security-jar.sh
|
||||
|
||||
echo "Setting permissions and ownership for necessary directories..."
|
||||
# Attempt to change ownership of directories and files
|
||||
if chown -R stirlingpdfuser:stirlingpdfgroup $HOME /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar; then
|
||||
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar || true
|
||||
# If chown succeeds, execute the command as stirlingpdfuser
|
||||
exec su-exec stirlingpdfuser "$@"
|
||||
else
|
||||
# If chown fails, execute the command without changing the user context
|
||||
echo "[WARN] Chown failed, running as host user"
|
||||
exec "$@"
|
||||
fi
|
||||
/scripts/init-without-ocr.sh "$@"
|
67
scripts/installFonts.sh
Normal file
67
scripts/installFonts.sh
Normal file
|
@ -0,0 +1,67 @@
|
|||
#!/bin/bash
|
||||
|
||||
LANGS=$1
|
||||
|
||||
# Function to install a font package
|
||||
install_font() {
|
||||
echo "Installing font package: $1"
|
||||
if ! apk add "$1" --no-cache; then
|
||||
echo "Failed to install $1"
|
||||
fi
|
||||
}
|
||||
|
||||
# Install common fonts used across many languages
|
||||
#common_fonts=(
|
||||
# font-terminus
|
||||
# font-dejavu
|
||||
# font-noto
|
||||
# font-noto-cjk
|
||||
# font-awesome
|
||||
# font-noto-extra
|
||||
#)
|
||||
#
|
||||
#for font in "${common_fonts[@]}"; do
|
||||
# install_font $font
|
||||
#done
|
||||
|
||||
# Map languages to specific font packages
|
||||
declare -A language_fonts=(
|
||||
["ar_AR"]="font-noto-arabic"
|
||||
["zh_CN"]="font-isas-misc"
|
||||
["zh_TW"]="font-isas-misc"
|
||||
["ja_JP"]="font-noto font-noto-thai font-noto-tibetan font-ipa font-sony-misc font-jis-misc"
|
||||
["ru_RU"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
||||
["sr_LATN_RS"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
||||
["uk_UA"]="font-vollkorn font-misc-cyrillic font-mutt-misc font-screen-cyrillic font-winitzki-cyrillic font-cronyx-cyrillic"
|
||||
["ko_KR"]="font-noto font-noto-thai font-noto-tibetan"
|
||||
["el_GR"]="font-noto"
|
||||
["hi_IN"]="font-noto-devanagari"
|
||||
["bg_BG"]="font-vollkorn font-misc-cyrillic"
|
||||
["GENERAL"]="font-terminus font-dejavu font-noto font-noto-cjk font-awesome font-noto-extra"
|
||||
)
|
||||
|
||||
# Install fonts for other languages which generally do not need special packages beyond 'font-noto'
|
||||
other_langs=("en_GB" "en_US" "de_DE" "fr_FR" "es_ES" "ca_CA" "it_IT" "pt_BR" "nl_NL" "sv_SE" "pl_PL" "ro_RO" "hu_HU" "tr_TR" "id_ID" "eu_ES")
|
||||
if [[ $LANGS == "ALL" ]]; then
|
||||
# Install all fonts from the language_fonts map
|
||||
for fonts in "${language_fonts[@]}"; do
|
||||
for font in $fonts; do
|
||||
install_font $font
|
||||
done
|
||||
done
|
||||
else
|
||||
# Split comma-separated languages and install necessary fonts
|
||||
IFS=',' read -ra LANG_CODES <<< "$LANGS"
|
||||
for code in "${LANG_CODES[@]}"; do
|
||||
if [[ " ${other_langs[@]} " =~ " ${code} " ]]; then
|
||||
install_font font-noto
|
||||
else
|
||||
fonts_to_install=${language_fonts[$code]}
|
||||
if [ ! -z "$fonts_to_install" ]; then
|
||||
for font in $fonts_to_install; do
|
||||
install_font $font
|
||||
done
|
||||
fi
|
||||
fi
|
||||
done
|
||||
fi
|
154
scripts/translation_status.toml
Normal file
154
scripts/translation_status.toml
Normal file
|
@ -0,0 +1,154 @@
|
|||
[ar_AR]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[bg_BG]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[ca_CA]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[de_DE]
|
||||
ignore = [
|
||||
'AddStampRequest.alphabet',
|
||||
'AddStampRequest.position',
|
||||
'PDFToBook.selectText.1',
|
||||
'PDFToText.tags',
|
||||
'addPageNumbers.selectText.3',
|
||||
'alphabet',
|
||||
'certSign.name',
|
||||
'language.direction',
|
||||
'licenses.version',
|
||||
'pipeline.title',
|
||||
'pipelineOptions.pipelineHeader',
|
||||
'sponsor',
|
||||
'text',
|
||||
'watermark.type.1',
|
||||
]
|
||||
|
||||
[el_GR]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[es_ES]
|
||||
ignore = [
|
||||
'adminUserSettings.roles',
|
||||
'color',
|
||||
'language.direction',
|
||||
'no',
|
||||
'showJS.tags',
|
||||
]
|
||||
|
||||
[eu_ES]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[fr_FR]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[hi_IN]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[hu_HU]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[id_ID]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[it_IT]
|
||||
ignore = [
|
||||
'font',
|
||||
'language.direction',
|
||||
'no',
|
||||
'password',
|
||||
'pipeline.title',
|
||||
'pipelineOptions.pipelineHeader',
|
||||
'removePassword.selectText.2',
|
||||
'showJS.tags',
|
||||
'sponsor',
|
||||
]
|
||||
|
||||
[ja_JP]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[ko_KR]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[nl_NL]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[pl_PL]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[pt_BR]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[pt_PT]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[ro_RO]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[ru_RU]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[sr_LATN_RS]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[sv_SE]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[tr_TR]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[uk_UA]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[zh_CN]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
||||
|
||||
[zh_TW]
|
||||
ignore = [
|
||||
'language.direction',
|
||||
]
|
|
@ -5,6 +5,8 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -62,16 +64,39 @@ public class SPdfApplication {
|
|||
}
|
||||
|
||||
public static void main(String[] args) throws IOException, InterruptedException {
|
||||
|
||||
SpringApplication app = new SpringApplication(SPdfApplication.class);
|
||||
app.addInitializers(new ConfigInitializer());
|
||||
Map<String, String> propertyFiles = new HashMap<>();
|
||||
|
||||
// stirling pdf settings file
|
||||
if (Files.exists(Paths.get("configs/settings.yml"))) {
|
||||
app.setDefaultProperties(
|
||||
Collections.singletonMap(
|
||||
"spring.config.additional-location", "file:configs/settings.yml"));
|
||||
propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
|
||||
} else {
|
||||
logger.warn(
|
||||
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead.");
|
||||
}
|
||||
|
||||
// custom javs settings file
|
||||
if (Files.exists(Paths.get("configs/custom_settings.yml"))) {
|
||||
String existing = propertyFiles.getOrDefault("spring.config.additional-location", "");
|
||||
if (!existing.isEmpty()) {
|
||||
existing += ",";
|
||||
}
|
||||
propertyFiles.put(
|
||||
"spring.config.additional-location",
|
||||
existing + "file:configs/custom_settings.yml");
|
||||
} else {
|
||||
logger.warn("Custom configuration file 'configs/custom_settings.yml' does not exist.");
|
||||
}
|
||||
|
||||
if (!propertyFiles.isEmpty()) {
|
||||
app.setDefaultProperties(
|
||||
Collections.singletonMap(
|
||||
"spring.config.additional-location",
|
||||
propertyFiles.get("spring.config.additional-location")));
|
||||
}
|
||||
|
||||
app.run(args);
|
||||
|
||||
try {
|
||||
|
|
|
@ -6,18 +6,35 @@ import java.nio.file.Paths;
|
|||
import java.util.Properties;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.thymeleaf.spring6.SpringTemplateEngine;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Configuration
|
||||
@Lazy
|
||||
public class AppConfig {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
name = "system.customHTMLFiles",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public SpringTemplateEngine templateEngine(ResourceLoader resourceLoader) {
|
||||
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
|
||||
templateEngine.addTemplateResolver(new FileFallbackTemplateResolver(resourceLoader));
|
||||
return templateEngine;
|
||||
}
|
||||
|
||||
@Bean(name = "loginEnabled")
|
||||
public boolean loginEnabled() {
|
||||
return applicationProperties.getSecurity().getEnableLogin();
|
||||
|
@ -85,4 +102,10 @@ public class AppConfig {
|
|||
}
|
||||
return "true".equalsIgnoreCase(installOps);
|
||||
}
|
||||
|
||||
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
|
||||
@Bean(name = "activSecurity")
|
||||
public boolean missingActivSecurity() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package stirling.software.SPDF.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
|
||||
@Service
|
||||
class AppUpdateService {
|
||||
|
||||
@Autowired private ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired(required = false)
|
||||
ShowAdminInterface showAdmin;
|
||||
|
||||
@Bean(name = "shouldShow")
|
||||
@Scope("request")
|
||||
public boolean shouldShow() {
|
||||
boolean showUpdate = applicationProperties.getSystem().getShowUpdate();
|
||||
boolean showAdminResult = (showAdmin != null) ? showAdmin.getShowUpdateOnlyAdmins() : true;
|
||||
return showUpdate && showAdminResult;
|
||||
}
|
||||
}
|
|
@ -1,20 +1,20 @@
|
|||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.context.ApplicationContextInitializer;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
|
@ -26,12 +26,12 @@ public class ConfigInitializer
|
|||
public void initialize(ConfigurableApplicationContext applicationContext) {
|
||||
try {
|
||||
ensureConfigExists();
|
||||
} catch (IOException e) {
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to initialize application configuration", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void ensureConfigExists() throws IOException {
|
||||
public void ensureConfigExists() throws IOException, URISyntaxException {
|
||||
// Define the path to the external config directory
|
||||
Path destPath = Paths.get("configs", "settings.yml");
|
||||
|
||||
|
@ -51,170 +51,152 @@ public class ConfigInitializer
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// If user file exists, we need to merge it with the template from the classpath
|
||||
List<String> templateLines;
|
||||
try (InputStream in =
|
||||
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) {
|
||||
templateLines =
|
||||
new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))
|
||||
.lines()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
Path templatePath =
|
||||
Paths.get(
|
||||
getClass()
|
||||
.getClassLoader()
|
||||
.getResource("settings.yml.template")
|
||||
.toURI());
|
||||
Path userPath = Paths.get("configs", "settings.yml");
|
||||
|
||||
mergeYamlFiles(templateLines, destPath, destPath);
|
||||
List<String> templateLines = Files.readAllLines(templatePath);
|
||||
List<String> userLines =
|
||||
Files.exists(userPath) ? Files.readAllLines(userPath) : new ArrayList<>();
|
||||
|
||||
Map<String, String> templateEntries = extractEntries(templateLines);
|
||||
Map<String, String> userEntries = extractEntries(userLines);
|
||||
|
||||
List<String> mergedLines = mergeConfigs(templateLines, templateEntries, userEntries);
|
||||
mergedLines = cleanInvalidYamlEntries(mergedLines);
|
||||
Files.write(userPath, mergedLines);
|
||||
}
|
||||
|
||||
Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
|
||||
if (!Files.exists(customSettingsPath)) {
|
||||
Files.createFile(customSettingsPath);
|
||||
}
|
||||
}
|
||||
private static Map<String, String> extractEntries(List<String> lines) {
|
||||
Map<String, String> entries = new HashMap<>();
|
||||
StringBuilder currentEntry = new StringBuilder();
|
||||
String currentKey = null;
|
||||
int blockIndent = -1;
|
||||
|
||||
public void mergeYamlFiles(List<String> templateLines, Path userFilePath, Path outputPath)
|
||||
throws IOException {
|
||||
List<String> userLines = Files.readAllLines(userFilePath);
|
||||
List<String> mergedLines = new ArrayList<>();
|
||||
boolean insideAutoGenerated = false;
|
||||
boolean beforeFirstKey = true;
|
||||
for (String line : lines) {
|
||||
if (line.trim().isEmpty()) {
|
||||
if (currentKey != null) {
|
||||
currentEntry.append(line).append("\n");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Function<String, Boolean> isCommented = line -> line.trim().startsWith("#");
|
||||
Function<String, String> extractKey =
|
||||
line -> {
|
||||
String[] parts = line.split(":");
|
||||
return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
|
||||
};
|
||||
|
||||
Function<String, Integer> getIndentationLevel =
|
||||
line -> {
|
||||
int count = 0;
|
||||
for (char ch : line.toCharArray()) {
|
||||
if (ch == ' ') count++;
|
||||
else break;
|
||||
int indentLevel = getIndentationLevel(line);
|
||||
if (line.trim().startsWith("#")) {
|
||||
if (indentLevel <= blockIndent || blockIndent == -1) {
|
||||
if (currentKey != null) {
|
||||
entries.put(currentKey, currentEntry.toString().trim());
|
||||
currentEntry = new StringBuilder();
|
||||
}
|
||||
return count;
|
||||
};
|
||||
currentKey = line.trim().replaceAll("#", "").split(":")[0].trim();
|
||||
blockIndent = indentLevel;
|
||||
}
|
||||
currentEntry.append(line).append("\n");
|
||||
} else if (indentLevel == 0 || indentLevel <= blockIndent) {
|
||||
if (currentKey != null) {
|
||||
entries.put(currentKey, currentEntry.toString().trim());
|
||||
currentEntry = new StringBuilder();
|
||||
}
|
||||
currentKey = line.split(":")[0].trim();
|
||||
blockIndent = indentLevel;
|
||||
currentEntry.append(line).append("\n");
|
||||
} else {
|
||||
currentEntry.append(line).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
|
||||
if (currentKey != null) {
|
||||
entries.put(currentKey, currentEntry.toString().trim());
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
|
||||
private static List<String> mergeConfigs(List<String> templateLines, Map<String, String> templateEntries, Map<String, String> userEntries) {
|
||||
List<String> mergedLines = new ArrayList<>();
|
||||
Set<String> handledKeys = new HashSet<>();
|
||||
|
||||
String currentBlockKey = null;
|
||||
int blockIndent = -1;
|
||||
|
||||
for (String line : templateLines) {
|
||||
String key = extractKey.apply(line);
|
||||
|
||||
if ("AutomaticallyGenerated:".equalsIgnoreCase(line.trim())) {
|
||||
insideAutoGenerated = true;
|
||||
mergedLines.add(line);
|
||||
continue;
|
||||
} else if (insideAutoGenerated && line.trim().isEmpty()) {
|
||||
insideAutoGenerated = false;
|
||||
if (line.trim().isEmpty()) {
|
||||
mergedLines.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) {
|
||||
// Handle top comments and empty lines before the first key.
|
||||
int indentLevel = getIndentationLevel(line);
|
||||
if (indentLevel == 0 || (indentLevel <= blockIndent && !line.trim().startsWith("#"))) {
|
||||
currentBlockKey = line.split(":")[0].trim();
|
||||
blockIndent = indentLevel;
|
||||
}
|
||||
|
||||
if (userEntries.containsKey(currentBlockKey) && !handledKeys.contains(currentBlockKey)) {
|
||||
mergedLines.add(userEntries.get(currentBlockKey));
|
||||
handledKeys.add(currentBlockKey);
|
||||
} else if (!handledKeys.contains(currentBlockKey)) {
|
||||
mergedLines.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!key.isEmpty()) beforeFirstKey = false;
|
||||
|
||||
if (userKeys.contains(key)) {
|
||||
// If user has any version (commented or uncommented) of this key, skip the
|
||||
// template line
|
||||
Optional<String> userValue =
|
||||
userLines.stream()
|
||||
.filter(
|
||||
l ->
|
||||
extractKey.apply(l).equalsIgnoreCase(key)
|
||||
&& !isCommented.apply(l))
|
||||
.findFirst();
|
||||
if (userValue.isPresent()) mergedLines.add(userValue.get());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isCommented.apply(line) || line.trim().isEmpty() || !userKeys.contains(key)) {
|
||||
mergedLines.add(
|
||||
line); // If line is commented, empty or key not present in user's file,
|
||||
// retain the
|
||||
// template line
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Add any additional uncommented user lines that are not present in the
|
||||
// template
|
||||
for (String userLine : userLines) {
|
||||
String userKey = extractKey.apply(userLine);
|
||||
boolean isPresentInTemplate =
|
||||
templateLines.stream()
|
||||
.map(extractKey)
|
||||
.anyMatch(templateKey -> templateKey.equalsIgnoreCase(userKey));
|
||||
if (!isPresentInTemplate && !isCommented.apply(userLine)) {
|
||||
if (!childOfTemplateEntry(
|
||||
isCommented,
|
||||
extractKey,
|
||||
getIndentationLevel,
|
||||
userLines,
|
||||
userLine,
|
||||
templateLines)) {
|
||||
// check if userLine is a child of a entry within templateLines or not, if child
|
||||
// of parent in templateLines then dont add to mergedLines, if anything else
|
||||
// then add
|
||||
mergedLines.add(userLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Files.write(outputPath, mergedLines, StandardCharsets.UTF_8);
|
||||
return mergedLines;
|
||||
}
|
||||
|
||||
// New method to check if a userLine is a child of an entry in templateLines
|
||||
boolean childOfTemplateEntry(
|
||||
Function<String, Boolean> isCommented,
|
||||
Function<String, String> extractKey,
|
||||
Function<String, Integer> getIndentationLevel,
|
||||
List<String> userLines,
|
||||
String userLine,
|
||||
List<String> templateLines) {
|
||||
String userKey = extractKey.apply(userLine).trim();
|
||||
int userIndentation = getIndentationLevel.apply(userLine);
|
||||
|
||||
// Start by assuming the line is not a child of an entry in templateLines
|
||||
boolean isChild = false;
|
||||
|
||||
// Iterate backwards through userLines from the current line to find any parent
|
||||
for (int i = userLines.indexOf(userLine) - 1; i >= 0; i--) {
|
||||
String potentialParentLine = userLines.get(i);
|
||||
int parentIndentation = getIndentationLevel.apply(potentialParentLine);
|
||||
private static List<String> cleanInvalidYamlEntries(List<String> lines) {
|
||||
List<String> cleanedLines = new ArrayList<>();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
String line = lines.get(i);
|
||||
String trimmedLine = line.trim();
|
||||
|
||||
// Check if we've reached a potential parent based on indentation
|
||||
if (parentIndentation < userIndentation) {
|
||||
String parentKey = extractKey.apply(potentialParentLine).trim();
|
||||
|
||||
// Now, check if this potential parent or any of its parents exist in templateLines
|
||||
boolean parentExistsInTemplate =
|
||||
templateLines.stream()
|
||||
.filter(line -> !isCommented.apply(line)) // Skip commented lines
|
||||
.anyMatch(
|
||||
templateLine -> {
|
||||
String templateKey =
|
||||
extractKey.apply(templateLine).trim();
|
||||
return parentKey.equalsIgnoreCase(templateKey);
|
||||
});
|
||||
|
||||
if (!parentExistsInTemplate) {
|
||||
// If the parent does not exist in template, check the next level parent
|
||||
userIndentation =
|
||||
parentIndentation; // Update userIndentation to the parent's indentation
|
||||
// for next iteration
|
||||
if (parentIndentation == 0) {
|
||||
// If we've reached the top-level parent and it's not in template, the
|
||||
// original line is considered not a child
|
||||
isChild = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// If any parent exists in template, the original line is considered a child
|
||||
isChild = true;
|
||||
break;
|
||||
}
|
||||
if (trimmedLine.startsWith("#")
|
||||
|| !trimmedLine.endsWith(":")
|
||||
|| trimmedLine.contains(" ")) {
|
||||
cleanedLines.add(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isKeyWithoutChildrenOrValue(i, lines)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cleanedLines.add(line);
|
||||
}
|
||||
return cleanedLines;
|
||||
}
|
||||
|
||||
private static boolean isKeyWithoutChildrenOrValue(int currentIndex, List<String> lines) {
|
||||
if (currentIndex + 1 < lines.size()) {
|
||||
String currentLine = lines.get(currentIndex);
|
||||
String nextLine = lines.get(currentIndex + 1);
|
||||
int currentIndentation = getIndentationLevel(currentLine);
|
||||
int nextIndentation = getIndentationLevel(nextLine);
|
||||
|
||||
// If the next line is less or equally indented, it's not a child or value
|
||||
return nextIndentation <= currentIndentation;
|
||||
}
|
||||
|
||||
return isChild; // Return true if the line is not a child of any entry in templateLines
|
||||
// If it's the last line, then it definitely has no children or value
|
||||
return true;
|
||||
}
|
||||
|
||||
private static int getIndentationLevel(String line) {
|
||||
int count = 0;
|
||||
for (char ch : line.toCharArray()) {
|
||||
if (ch == ' ') count++;
|
||||
else break;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,7 +146,6 @@ public class EndpointConfiguration {
|
|||
addEndpointToGroup("CLI", "xlsx-to-pdf");
|
||||
addEndpointToGroup("CLI", "pdf-to-word");
|
||||
addEndpointToGroup("CLI", "pdf-to-presentation");
|
||||
addEndpointToGroup("CLI", "pdf-to-text");
|
||||
addEndpointToGroup("CLI", "pdf-to-html");
|
||||
addEndpointToGroup("CLI", "pdf-to-xml");
|
||||
addEndpointToGroup("CLI", "ocr-pdf");
|
||||
|
@ -154,6 +153,7 @@ public class EndpointConfiguration {
|
|||
addEndpointToGroup("CLI", "url-to-pdf");
|
||||
addEndpointToGroup("CLI", "book-to-pdf");
|
||||
addEndpointToGroup("CLI", "pdf-to-book");
|
||||
addEndpointToGroup("CLI", "pdf-to-rtf");
|
||||
|
||||
// Calibre
|
||||
addEndpointToGroup("Calibre", "book-to-pdf");
|
||||
|
@ -175,7 +175,7 @@ public class EndpointConfiguration {
|
|||
addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
|
||||
addEndpointToGroup("LibreOffice", "pdf-to-word");
|
||||
addEndpointToGroup("LibreOffice", "pdf-to-presentation");
|
||||
addEndpointToGroup("LibreOffice", "pdf-to-text");
|
||||
addEndpointToGroup("LibreOffice", "pdf-to-rtf");
|
||||
addEndpointToGroup("LibreOffice", "pdf-to-html");
|
||||
addEndpointToGroup("LibreOffice", "pdf-to-xml");
|
||||
|
||||
|
@ -218,6 +218,7 @@ public class EndpointConfiguration {
|
|||
addEndpointToGroup("Java", "overlay-pdf");
|
||||
addEndpointToGroup("Java", "split-pdf-by-sections");
|
||||
addEndpointToGroup("Java", REMOVE_BLANKS);
|
||||
addEndpointToGroup("Java", "pdf-to-text");
|
||||
|
||||
// Javascript
|
||||
addEndpointToGroup("Javascript", "pdf-organizer");
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package stirling.software.SPDF.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.ResourceLoader;
|
||||
import org.thymeleaf.IEngineConfiguration;
|
||||
import org.thymeleaf.templateresolver.AbstractConfigurableTemplateResolver;
|
||||
import org.thymeleaf.templateresource.ClassLoaderTemplateResource;
|
||||
import org.thymeleaf.templateresource.FileTemplateResource;
|
||||
import org.thymeleaf.templateresource.ITemplateResource;
|
||||
|
||||
public class FileFallbackTemplateResolver extends AbstractConfigurableTemplateResolver {
|
||||
|
||||
private final ResourceLoader resourceLoader;
|
||||
|
||||
public FileFallbackTemplateResolver(ResourceLoader resourceLoader) {
|
||||
super();
|
||||
this.resourceLoader = resourceLoader;
|
||||
setSuffix(".html");
|
||||
}
|
||||
|
||||
// Note this does not work in local IDE, Prod jar only.
|
||||
@Override
|
||||
protected ITemplateResource computeTemplateResource(
|
||||
IEngineConfiguration configuration,
|
||||
String ownerTemplate,
|
||||
String template,
|
||||
String resourceName,
|
||||
String characterEncoding,
|
||||
Map<String, Object> templateResolutionAttributes) {
|
||||
Resource resource =
|
||||
resourceLoader.getResource("file:./customFiles/templates/" + resourceName);
|
||||
try {
|
||||
if (resource.exists() && resource.isReadable()) {
|
||||
return new FileTemplateResource(resource.getFile().getPath(), characterEncoding);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
|
||||
return new ClassLoaderTemplateResource(
|
||||
Thread.currentThread().getContextClassLoader(),
|
||||
"classpath:/templates/" + resourceName,
|
||||
characterEncoding);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
package stirling.software.SPDF.config;
|
||||
|
||||
public interface ShowAdminInterface {
|
||||
default boolean getShowUpdateOnlyAdmins() {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import stirling.software.SPDF.config.ShowAdminInterface;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.repository.UserRepository;
|
||||
|
||||
@Service
|
||||
class AppUpdateAuthService implements ShowAdminInterface {
|
||||
|
||||
@Autowired private UserRepository userRepository;
|
||||
@Autowired private ApplicationProperties applicationProperties;
|
||||
|
||||
public boolean getShowUpdateOnlyAdmins() {
|
||||
boolean showUpdate = applicationProperties.getSystem().getShowUpdate();
|
||||
if (!showUpdate) {
|
||||
return showUpdate;
|
||||
}
|
||||
|
||||
boolean showUpdateOnlyAdmin = applicationProperties.getSystem().getShowUpdateOnlyAdmin();
|
||||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
|
||||
if (authentication == null || !authentication.isAuthenticated()) {
|
||||
return !showUpdateOnlyAdmin;
|
||||
}
|
||||
|
||||
if (authentication.getName().equalsIgnoreCase("anonymousUser")) {
|
||||
return !showUpdateOnlyAdmin;
|
||||
}
|
||||
|
||||
Optional<User> user = userRepository.findByUsername(authentication.getName());
|
||||
if (user.isPresent() && showUpdateOnlyAdmin) {
|
||||
return "ROLE_ADMIN".equals(user.get().getRolesAsString());
|
||||
}
|
||||
|
||||
return showUpdate;
|
||||
}
|
||||
}
|
|
@ -56,7 +56,7 @@ public class CustomAuthenticationFailureHandler extends SimpleUrlAuthenticationF
|
|||
}
|
||||
|
||||
private boolean isDemoUser(String username) {
|
||||
Optional<User> user = userService.findByUsername(username);
|
||||
Optional<User> user = userService.findByUsernameIgnoreCase(username);
|
||||
return user.isPresent()
|
||||
&& user.get().getAuthorities().stream()
|
||||
.anyMatch(authority -> "ROLE_DEMO_USER".equals(authority.getAuthority()));
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
|
||||
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||
@Bean
|
||||
public SessionRegistry sessionRegistry() {
|
||||
return new SessionRegistryImpl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLogoutSuccess(
|
||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||
throws IOException, ServletException {
|
||||
HttpSession session = request.getSession(false);
|
||||
if (session != null) {
|
||||
String sessionId = session.getId();
|
||||
sessionRegistry().removeSessionInformation(sessionId);
|
||||
}
|
||||
|
||||
if (request.getParameter("oauth2AutoCreateDisabled") != null) {
|
||||
response.sendRedirect(
|
||||
request.getContextPath() + "/login?error=oauth2AutoCreateDisabled");
|
||||
} else {
|
||||
response.sendRedirect(request.getContextPath() + "/login?logout=true");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ public class FirstLoginFilter extends OncePerRequestFilter {
|
|||
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
Optional<User> user = userService.findByUsername(authentication.getName());
|
||||
Optional<User> user = userService.findByUsernameIgnoreCase(authentication.getName());
|
||||
if ("GET".equalsIgnoreCase(method)
|
||||
&& user.isPresent()
|
||||
&& user.get().isFirstLogin()
|
||||
|
|
|
@ -38,7 +38,7 @@ public class InitialSecuritySetup {
|
|||
initialUsername, initialPassword, Role.ADMIN.getRoleId(), true);
|
||||
}
|
||||
}
|
||||
if (!userService.usernameExists(Role.INTERNAL_API_USER.getRoleId())) {
|
||||
if (!userService.usernameExistsIgnoreCase(Role.INTERNAL_API_USER.getRoleId())) {
|
||||
userService.saveUser(
|
||||
Role.INTERNAL_API_USER.getRoleId(),
|
||||
UUID.randomUUID().toString(),
|
||||
|
@ -50,7 +50,7 @@ public class InitialSecuritySetup {
|
|||
@PostConstruct
|
||||
public void initSecretKey() throws IOException {
|
||||
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey();
|
||||
if (secretKey == null || secretKey.isEmpty()) {
|
||||
if (!isValidUUID(secretKey)) {
|
||||
secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
|
||||
saveKeyToConfig(secretKey);
|
||||
}
|
||||
|
@ -85,4 +85,16 @@ public class InitialSecuritySetup {
|
|||
// Write back to the file
|
||||
Files.write(path, lines);
|
||||
}
|
||||
private boolean isValidUUID(String uuid) {
|
||||
if (uuid == null) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
UUID.fromString(uuid);
|
||||
return true;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
package stirling.software.SPDF.config.security;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -10,18 +14,34 @@ import org.springframework.security.config.annotation.method.configuration.Enabl
|
|||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrations;
|
||||
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
|
||||
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
|
||||
import org.springframework.security.web.savedrequest.NullRequestCache;
|
||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||
|
||||
import jakarta.servlet.ServletException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||
|
||||
@Configuration
|
||||
|
@ -42,6 +62,8 @@ public class SecurityConfiguration {
|
|||
@Qualifier("loginEnabled")
|
||||
public boolean loginEnabledValue;
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@Autowired private UserAuthenticationFilter userAuthenticationFilter;
|
||||
|
||||
@Autowired private LoginAttemptService loginAttemptService;
|
||||
|
@ -87,7 +109,7 @@ public class SecurityConfiguration {
|
|||
logout ->
|
||||
logout.logoutRequestMatcher(
|
||||
new AntPathRequestMatcher("/logout"))
|
||||
.logoutSuccessUrl("/login?logout=true")
|
||||
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
|
||||
.invalidateHttpSession(true) // Invalidate session
|
||||
.deleteCookies("JSESSIONID", "remember-me")
|
||||
.addLogoutHandler(
|
||||
|
@ -124,6 +146,7 @@ public class SecurityConfiguration {
|
|||
: uri;
|
||||
|
||||
return trimmedUri.startsWith("/login")
|
||||
|| trimmedUri.startsWith("/oauth")
|
||||
|| trimmedUri.endsWith(".svg")
|
||||
|| trimmedUri.startsWith(
|
||||
"/register")
|
||||
|
@ -140,6 +163,49 @@ public class SecurityConfiguration {
|
|||
.authenticated())
|
||||
.userDetailsService(userDetailsService)
|
||||
.authenticationProvider(authenticationProvider());
|
||||
|
||||
// Handle OAUTH2 Logins
|
||||
if (applicationProperties.getSecurity().getOAUTH2().getEnabled()) {
|
||||
|
||||
http.oauth2Login(
|
||||
oauth2 ->
|
||||
oauth2.loginPage("/oauth2")
|
||||
/*
|
||||
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
||||
If user exists, login proceeds as usual. If user does not exist, then it is autocreated but only if 'OAUTH2AutoCreateUser'
|
||||
is set as true, else login fails with an error message advising the same.
|
||||
*/
|
||||
.successHandler(
|
||||
new AuthenticationSuccessHandler() {
|
||||
@Override
|
||||
public void onAuthenticationSuccess(
|
||||
HttpServletRequest request,
|
||||
HttpServletResponse response,
|
||||
Authentication authentication)
|
||||
throws ServletException, IOException {
|
||||
OAuth2User oauthUser =
|
||||
(OAuth2User)
|
||||
authentication
|
||||
.getPrincipal();
|
||||
if (userService.processOAuth2PostLogin(
|
||||
oauthUser.getAttribute("email"),
|
||||
applicationProperties
|
||||
.getSecurity()
|
||||
.getOAUTH2()
|
||||
.getAutoCreateUser())) {
|
||||
response.sendRedirect("/");
|
||||
} else {
|
||||
response.sendRedirect(
|
||||
"/logout?oauth2AutoCreateDisabled=true");
|
||||
}
|
||||
}
|
||||
})
|
||||
// Add existing Authorities from the database
|
||||
.userInfoEndpoint(
|
||||
userInfoEndpoint ->
|
||||
userInfoEndpoint.userAuthoritiesMapper(
|
||||
userAuthoritiesMapper())));
|
||||
}
|
||||
} else {
|
||||
http.csrf(csrf -> csrf.disable())
|
||||
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
|
||||
|
@ -148,6 +214,65 @@ public class SecurityConfiguration {
|
|||
return http.build();
|
||||
}
|
||||
|
||||
// Client Registration Repository for OAUTH2 OIDC Login
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "security.oauth2.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
||||
return new InMemoryClientRegistrationRepository(this.oidcClientRegistration());
|
||||
}
|
||||
|
||||
private ClientRegistration oidcClientRegistration() {
|
||||
return ClientRegistrations.fromOidcIssuerLocation(
|
||||
applicationProperties.getSecurity().getOAUTH2().getIssuer())
|
||||
.registrationId("oidc")
|
||||
.clientId(applicationProperties.getSecurity().getOAUTH2().getClientId())
|
||||
.clientSecret(applicationProperties.getSecurity().getOAUTH2().getClientSecret())
|
||||
.scope("openid", "profile", "email")
|
||||
.userNameAttributeName("email")
|
||||
.clientName("OIDC")
|
||||
.build();
|
||||
}
|
||||
|
||||
/*
|
||||
This following function is to grant Authorities to the OAUTH2 user from the values stored in the database.
|
||||
This is required for the internal; 'hasRole()' function to give out the correct role.
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
value = "security.oauth2.enabled",
|
||||
havingValue = "true",
|
||||
matchIfMissing = false)
|
||||
GrantedAuthoritiesMapper userAuthoritiesMapper() {
|
||||
return (authorities) -> {
|
||||
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
|
||||
|
||||
authorities.forEach(
|
||||
authority -> {
|
||||
// Add existing OAUTH2 Authorities
|
||||
mappedAuthorities.add(new SimpleGrantedAuthority(authority.getAuthority()));
|
||||
|
||||
// Add Authorities from database for existing user, if user is present.
|
||||
if (authority instanceof OAuth2UserAuthority oauth2Auth) {
|
||||
Optional<User> userOpt =
|
||||
userService.findByUsernameIgnoreCase(
|
||||
(String) oauth2Auth.getAttributes().get("email"));
|
||||
if (userOpt.isPresent()) {
|
||||
User user = userOpt.get();
|
||||
if (user != null) {
|
||||
mappedAuthorities.add(
|
||||
new SimpleGrantedAuthority(
|
||||
userService.findRole(user).getAuthority()));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return mappedAuthorities;
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IPRateLimitingFilter rateLimitingFilter() {
|
||||
int maxRequestsPerIp = 1000000; // Example limit TODO add config level
|
||||
|
@ -166,4 +291,9 @@ public class SecurityConfiguration {
|
|||
public PersistentTokenRepository persistentTokenRepository() {
|
||||
return new JPATokenRepositoryImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public boolean activSecurity() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
|||
import stirling.software.SPDF.model.Authority;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
import stirling.software.SPDF.model.User;
|
||||
import stirling.software.SPDF.repository.AuthorityRepository;
|
||||
import stirling.software.SPDF.repository.UserRepository;
|
||||
|
||||
@Service
|
||||
|
@ -28,8 +29,28 @@ public class UserService implements UserServiceInterface {
|
|||
|
||||
@Autowired private UserRepository userRepository;
|
||||
|
||||
@Autowired private AuthorityRepository authorityRepository;
|
||||
|
||||
@Autowired private PasswordEncoder passwordEncoder;
|
||||
|
||||
// Handle OAUTH2 login and user auto creation.
|
||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser) {
|
||||
Optional<User> existUser = userRepository.findByUsernameIgnoreCase(username);
|
||||
if (existUser.isPresent()) {
|
||||
return true;
|
||||
}
|
||||
if (autoCreateUser) {
|
||||
User user = new User();
|
||||
user.setUsername(username);
|
||||
user.setEnabled(true);
|
||||
user.setFirstLogin(false);
|
||||
user.addAuthority(new Authority(Role.USER.getRoleId(), user));
|
||||
userRepository.save(user);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public Authentication getAuthentication(String apiKey) {
|
||||
User user = getUserByApiKey(apiKey);
|
||||
if (user == null) {
|
||||
|
@ -62,7 +83,7 @@ public class UserService implements UserServiceInterface {
|
|||
public User addApiKeyToUser(String username) {
|
||||
User user =
|
||||
userRepository
|
||||
.findByUsername(username)
|
||||
.findByUsernameIgnoreCase(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
|
||||
user.setApiKey(generateApiKey());
|
||||
|
@ -76,7 +97,7 @@ public class UserService implements UserServiceInterface {
|
|||
public String getApiKeyForUser(String username) {
|
||||
User user =
|
||||
userRepository
|
||||
.findByUsername(username)
|
||||
.findByUsernameIgnoreCase(username)
|
||||
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
|
||||
return user.getApiKey();
|
||||
}
|
||||
|
@ -103,7 +124,7 @@ public class UserService implements UserServiceInterface {
|
|||
}
|
||||
|
||||
public boolean validateApiKeyForUser(String username, String apiKey) {
|
||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
|
||||
return userOpt.isPresent() && userOpt.get().getApiKey().equals(apiKey);
|
||||
}
|
||||
|
||||
|
@ -136,7 +157,7 @@ public class UserService implements UserServiceInterface {
|
|||
}
|
||||
|
||||
public void deleteUser(String username) {
|
||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
|
||||
if (userOpt.isPresent()) {
|
||||
for (Authority authority : userOpt.get().getAuthorities()) {
|
||||
if (authority.getAuthority().equals(Role.INTERNAL_API_USER.getRoleId())) {
|
||||
|
@ -151,12 +172,16 @@ public class UserService implements UserServiceInterface {
|
|||
return userRepository.findByUsername(username).isPresent();
|
||||
}
|
||||
|
||||
public boolean usernameExistsIgnoreCase(String username) {
|
||||
return userRepository.findByUsernameIgnoreCase(username).isPresent();
|
||||
}
|
||||
|
||||
public boolean hasUsers() {
|
||||
return userRepository.count() > 0;
|
||||
}
|
||||
|
||||
public void updateUserSettings(String username, Map<String, String> updates) {
|
||||
Optional<User> userOpt = userRepository.findByUsername(username);
|
||||
Optional<User> userOpt = userRepository.findByUsernameIgnoreCase(username);
|
||||
if (userOpt.isPresent()) {
|
||||
User user = userOpt.get();
|
||||
Map<String, String> settingsMap = user.getSettings();
|
||||
|
@ -180,6 +205,10 @@ public class UserService implements UserServiceInterface {
|
|||
return userRepository.findByUsernameIgnoreCase(username);
|
||||
}
|
||||
|
||||
public Authority findRole(User user) {
|
||||
return authorityRepository.findByUserId(user.getId());
|
||||
}
|
||||
|
||||
public void changeUsername(User user, String newUsername) {
|
||||
user.setUsername(newUsername);
|
||||
userRepository.save(user);
|
||||
|
@ -195,6 +224,12 @@ public class UserService implements UserServiceInterface {
|
|||
userRepository.save(user);
|
||||
}
|
||||
|
||||
public void changeRole(User user, String newRole) {
|
||||
Authority userAuthority = this.findRole(user);
|
||||
userAuthority.setAuthority(newRole);
|
||||
authorityRepository.save(userAuthority);
|
||||
}
|
||||
|
||||
public boolean isPasswordCorrect(User user, String currentPassword) {
|
||||
return passwordEncoder.matches(currentPassword, user.getPassword());
|
||||
}
|
||||
|
|
|
@ -27,7 +27,9 @@ import io.github.pixee.security.Filenames;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
import stirling.software.SPDF.model.api.PDFWithPageNums;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
|
@ -49,6 +51,7 @@ public class SplitPDFController {
|
|||
// open the pdf document
|
||||
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
|
||||
int totalPages = document.getNumberOfPages();
|
||||
List<Integer> pageNumbers = request.getPageNumbersList(document, false);
|
||||
System.out.println(
|
||||
|
@ -75,6 +78,9 @@ public class SplitPDFController {
|
|||
}
|
||||
previousPageNumber = splitPoint + 1;
|
||||
|
||||
// Transfer metadata to split pdf
|
||||
PdfUtils.setMetadataToPdf(splitDocument, metadata);
|
||||
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
splitDocument.save(baos);
|
||||
|
||||
|
|
|
@ -4,8 +4,6 @@ import java.io.ByteArrayOutputStream;
|
|||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
|
@ -41,117 +39,137 @@ public class SplitPdfBySizeController {
|
|||
+ " if 10MB and each page is 1MB and you enter 2MB then 5 docs each 2MB (rounded so that it accepts 1.9MB but not 2.1MB) Input:PDF Output:ZIP-PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request)
|
||||
throws Exception {
|
||||
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
|
||||
|
||||
MultipartFile file = request.getFileInput();
|
||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes());
|
||||
|
||||
// 0 = size, 1 = page count, 2 = doc count
|
||||
int type = request.getSplitType();
|
||||
String value = request.getSplitValue();
|
||||
|
||||
if (type == 0) { // Split by size
|
||||
long maxBytes = GeneralUtils.convertSizeToBytes(value);
|
||||
long currentSize = 0;
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
|
||||
for (PDPage page : sourceDocument.getPages()) {
|
||||
ByteArrayOutputStream pageOutputStream = new ByteArrayOutputStream();
|
||||
PDDocument tempDoc = new PDDocument();
|
||||
tempDoc.addPage(page);
|
||||
tempDoc.save(pageOutputStream);
|
||||
tempDoc.close();
|
||||
|
||||
long pageSize = pageOutputStream.size();
|
||||
if (currentSize + pageSize > maxBytes) {
|
||||
// Save and reset current document
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
currentDoc = new PDDocument();
|
||||
currentSize = 0;
|
||||
}
|
||||
|
||||
currentDoc.addPage(page);
|
||||
currentSize += pageSize;
|
||||
}
|
||||
// Add the last document if it contains any pages
|
||||
if (currentDoc.getPages().getCount() != 0) {
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
}
|
||||
} else if (type == 1) { // Split by page count
|
||||
int pageCount = Integer.parseInt(value);
|
||||
int currentPageCount = 0;
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
|
||||
for (PDPage page : sourceDocument.getPages()) {
|
||||
currentDoc.addPage(page);
|
||||
currentPageCount++;
|
||||
|
||||
if (currentPageCount == pageCount) {
|
||||
// Save and reset current document
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
currentDoc = new PDDocument();
|
||||
currentPageCount = 0;
|
||||
}
|
||||
}
|
||||
// Add the last document if it contains any pages
|
||||
if (currentDoc.getPages().getCount() != 0) {
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
}
|
||||
} else if (type == 2) { // Split by doc count
|
||||
int documentCount = Integer.parseInt(value);
|
||||
int totalPageCount = sourceDocument.getNumberOfPages();
|
||||
int pagesPerDocument = totalPageCount / documentCount;
|
||||
int extraPages = totalPageCount % documentCount;
|
||||
int currentPageIndex = 0;
|
||||
|
||||
for (int i = 0; i < documentCount; i++) {
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
|
||||
|
||||
for (int j = 0; j < pagesToAdd; j++) {
|
||||
currentDoc.addPage(sourceDocument.getPage(currentPageIndex++));
|
||||
}
|
||||
|
||||
splitDocumentsBoas.add(currentDocToByteArray(currentDoc));
|
||||
}
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid argument for split type");
|
||||
}
|
||||
|
||||
sourceDocument.close();
|
||||
|
||||
Path zipFile = Files.createTempFile("split_documents", ".zip");
|
||||
String filename =
|
||||
Filenames.toSimpleFileName(file.getOriginalFilename())
|
||||
.replaceFirst("[.][^.]+$", "");
|
||||
byte[] data;
|
||||
byte[] data = null;
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile));
|
||||
PDDocument sourceDocument = Loader.loadPDF(file.getBytes())) {
|
||||
|
||||
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile))) {
|
||||
for (int i = 0; i < splitDocumentsBoas.size(); i++) {
|
||||
String fileName = filename + "_" + (i + 1) + ".pdf";
|
||||
ByteArrayOutputStream baos = splitDocumentsBoas.get(i);
|
||||
byte[] pdf = baos.toByteArray();
|
||||
int type = request.getSplitType();
|
||||
String value = request.getSplitValue();
|
||||
|
||||
ZipEntry pdfEntry = new ZipEntry(fileName);
|
||||
zipOut.putNextEntry(pdfEntry);
|
||||
zipOut.write(pdf);
|
||||
zipOut.closeEntry();
|
||||
if (type == 0) {
|
||||
long maxBytes = GeneralUtils.convertSizeToBytes(value);
|
||||
handleSplitBySize(sourceDocument, maxBytes, zipOut, filename);
|
||||
} else if (type == 1) {
|
||||
int pageCount = Integer.parseInt(value);
|
||||
handleSplitByPageCount(sourceDocument, pageCount, zipOut, filename);
|
||||
} else if (type == 2) {
|
||||
int documentCount = Integer.parseInt(value);
|
||||
handleSplitByDocCount(sourceDocument, documentCount, zipOut, filename);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid argument for split type");
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
data = Files.readAllBytes(zipFile);
|
||||
Files.delete(zipFile);
|
||||
Files.deleteIfExists(zipFile);
|
||||
}
|
||||
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
|
||||
private ByteArrayOutputStream currentDocToByteArray(PDDocument document) throws IOException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
document.save(baos);
|
||||
document.close();
|
||||
return baos;
|
||||
private void handleSplitBySize(
|
||||
PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
|
||||
throws IOException {
|
||||
long currentSize = 0;
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
int fileIndex = 1;
|
||||
|
||||
for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) {
|
||||
PDPage page = sourceDocument.getPage(pageIndex);
|
||||
ByteArrayOutputStream pageOutputStream = new ByteArrayOutputStream();
|
||||
|
||||
try (PDDocument tempDoc = new PDDocument()) {
|
||||
PDPage importedPage = tempDoc.importPage(page); // This creates a new PDPage object
|
||||
tempDoc.save(pageOutputStream);
|
||||
}
|
||||
|
||||
long pageSize = pageOutputStream.size();
|
||||
if (currentSize + pageSize > maxBytes) {
|
||||
if (currentDoc.getNumberOfPages() > 0) {
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
currentDoc.close(); // Make sure to close the document
|
||||
currentDoc = new PDDocument();
|
||||
currentSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
PDPage newPage = new PDPage(page.getCOSObject()); // Re-create the page
|
||||
currentDoc.addPage(newPage);
|
||||
currentSize += pageSize;
|
||||
}
|
||||
|
||||
if (currentDoc.getNumberOfPages() != 0) {
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
currentDoc.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSplitByPageCount(
|
||||
PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
|
||||
throws IOException {
|
||||
int currentPageCount = 0;
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
int fileIndex = 1;
|
||||
for (PDPage page : sourceDocument.getPages()) {
|
||||
currentDoc.addPage(page);
|
||||
currentPageCount++;
|
||||
|
||||
if (currentPageCount == pageCount) {
|
||||
// Save and reset current document
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
currentDoc = new PDDocument();
|
||||
currentPageCount = 0;
|
||||
}
|
||||
}
|
||||
// Add the last document if it contains any pages
|
||||
if (currentDoc.getPages().getCount() != 0) {
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleSplitByDocCount(
|
||||
PDDocument sourceDocument,
|
||||
int documentCount,
|
||||
ZipOutputStream zipOut,
|
||||
String baseFilename)
|
||||
throws IOException {
|
||||
int totalPageCount = sourceDocument.getNumberOfPages();
|
||||
int pagesPerDocument = totalPageCount / documentCount;
|
||||
int extraPages = totalPageCount % documentCount;
|
||||
int currentPageIndex = 0;
|
||||
int fileIndex = 1;
|
||||
for (int i = 0; i < documentCount; i++) {
|
||||
PDDocument currentDoc = new PDDocument();
|
||||
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
|
||||
|
||||
for (int j = 0; j < pagesToAdd; j++) {
|
||||
currentDoc.addPage(sourceDocument.getPage(currentPageIndex++));
|
||||
}
|
||||
|
||||
saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveDocumentToZip(
|
||||
PDDocument document, ZipOutputStream zipOut, String baseFilename, int index)
|
||||
throws IOException {
|
||||
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
|
||||
document.save(outStream);
|
||||
document.close(); // Close the document to free resources
|
||||
|
||||
// Create a new zip entry
|
||||
ZipEntry zipEntry = new ZipEntry(baseFilename + "_" + index + ".pdf");
|
||||
zipOut.putNextEntry(zipEntry);
|
||||
zipOut.write(outStream.toByteArray());
|
||||
zipOut.closeEntry();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ public class UserController {
|
|||
@PreAuthorize("!hasAuthority('ROLE_DEMO_USER')")
|
||||
@PostMapping("/register")
|
||||
public String register(@ModelAttribute UsernameAndPass requestModel, Model model) {
|
||||
if (userService.usernameExists(requestModel.getUsername())) {
|
||||
if (userService.usernameExistsIgnoreCase(requestModel.getUsername())) {
|
||||
model.addAttribute("error", "Username already exists");
|
||||
return "register";
|
||||
}
|
||||
|
@ -70,7 +70,8 @@ public class UserController {
|
|||
return new RedirectView("/account?messageType=notAuthenticated");
|
||||
}
|
||||
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||
// The username MUST be unique when renaming
|
||||
Optional<User> userOpt = userService.findByUsername(principal.getName());
|
||||
|
||||
if (userOpt == null || userOpt.isEmpty()) {
|
||||
return new RedirectView("/account?messageType=userNotFound");
|
||||
|
@ -113,7 +114,7 @@ public class UserController {
|
|||
return new RedirectView("/change-creds?messageType=notAuthenticated");
|
||||
}
|
||||
|
||||
Optional<User> userOpt = userService.findByUsername(principal.getName());
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||
|
||||
if (userOpt == null || userOpt.isEmpty()) {
|
||||
return new RedirectView("/change-creds?messageType=userNotFound");
|
||||
|
@ -146,7 +147,7 @@ public class UserController {
|
|||
return new RedirectView("/account?messageType=notAuthenticated");
|
||||
}
|
||||
|
||||
Optional<User> userOpt = userService.findByUsername(principal.getName());
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(principal.getName());
|
||||
|
||||
if (userOpt == null || userOpt.isEmpty()) {
|
||||
return new RedirectView("/account?messageType=userNotFound");
|
||||
|
@ -207,7 +208,7 @@ public class UserController {
|
|||
return new RedirectView("/addUsers?messageType=usernameExists");
|
||||
}
|
||||
}
|
||||
if (userService.usernameExists(username)) {
|
||||
if (userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=usernameExists");
|
||||
}
|
||||
try {
|
||||
|
@ -226,12 +227,51 @@ public class UserController {
|
|||
return new RedirectView("/addUsers"); // Redirect to account page after adding the user
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/admin/changeRole")
|
||||
public RedirectView changeRole(
|
||||
@RequestParam(name = "username") String username,
|
||||
@RequestParam(name = "role") String role,
|
||||
Authentication authentication) {
|
||||
|
||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(username);
|
||||
|
||||
if (!userOpt.isPresent()) {
|
||||
return new RedirectView("/addUsers?messageType=userNotFound");
|
||||
}
|
||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=userNotFound");
|
||||
}
|
||||
// Get the currently authenticated username
|
||||
String currentUsername = authentication.getName();
|
||||
|
||||
// Check if the provided username matches the current session's username
|
||||
if (currentUsername.equalsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=downgradeCurrentUser");
|
||||
}
|
||||
try {
|
||||
// Validate the role
|
||||
Role roleEnum = Role.fromString(role);
|
||||
if (roleEnum == Role.INTERNAL_API_USER) {
|
||||
// If the role is INTERNAL_API_USER, reject the request
|
||||
return new RedirectView("/addUsers?messageType=invalidRole");
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
// If the role ID is not valid, redirect with an error message
|
||||
return new RedirectView("/addUsers?messageType=invalidRole");
|
||||
}
|
||||
User user = userOpt.get();
|
||||
|
||||
userService.changeRole(user, role);
|
||||
return new RedirectView("/addUsers"); // Redirect to account page after adding the user
|
||||
}
|
||||
|
||||
@PreAuthorize("hasRole('ROLE_ADMIN')")
|
||||
@PostMapping("/admin/deleteUser/{username}")
|
||||
public RedirectView deleteUser(
|
||||
@PathVariable(name = "username") String username, Authentication authentication) {
|
||||
|
||||
if (!userService.usernameExists(username)) {
|
||||
if (!userService.usernameExistsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=deleteUsernameExists");
|
||||
}
|
||||
|
||||
|
@ -239,7 +279,7 @@ public class UserController {
|
|||
String currentUsername = authentication.getName();
|
||||
|
||||
// Check if the provided username matches the current session's username
|
||||
if (currentUsername.equals(username)) {
|
||||
if (currentUsername.equalsIgnoreCase(username)) {
|
||||
return new RedirectView("/addUsers?messageType=deleteCurrentUser");
|
||||
}
|
||||
invalidateUserSessions(username);
|
||||
|
|
|
@ -6,10 +6,6 @@ import java.net.URLConnection;
|
|||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.core.io.ByteArrayResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
|
@ -39,7 +35,7 @@ public class ConvertImgPDFController {
|
|||
summary = "Convert PDF to image(s)",
|
||||
description =
|
||||
"This endpoint converts a PDF file to image(s) with the specified image format, color type, and DPI. Users can choose to get a single image or multiple images. Input:PDF Output:Image Type:SI-Conditional")
|
||||
public ResponseEntity<Resource> convertToImage(@ModelAttribute ConvertToImageRequest request)
|
||||
public ResponseEntity<byte[]> convertToImage(@ModelAttribute ConvertToImageRequest request)
|
||||
throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String imageFormat = request.getImageFormat();
|
||||
|
@ -76,22 +72,15 @@ public class ConvertImgPDFController {
|
|||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (singleImage) {
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat)));
|
||||
ResponseEntity<Resource> response =
|
||||
new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
|
||||
return response;
|
||||
String docName = filename + "." + imageFormat;
|
||||
MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
|
||||
return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
|
||||
} else {
|
||||
ByteArrayResource resource = new ByteArrayResource(result);
|
||||
// return the Resource in the response
|
||||
return ResponseEntity.ok()
|
||||
.header(
|
||||
HttpHeaders.CONTENT_DISPOSITION,
|
||||
"attachment; filename=" + filename + "_convertedToImages.zip")
|
||||
.contentType(MediaType.APPLICATION_OCTET_STREAM)
|
||||
.contentLength(resource.contentLength())
|
||||
.body(resource);
|
||||
String zipFilename = filename + "_convertedToImages.zip";
|
||||
return WebResponseUtils.bytesToWebResponse(
|
||||
result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package stirling.software.SPDF.controller.api.converters;
|
||||
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.utils.PDFToFile;
|
||||
|
||||
@RestController
|
||||
@Tag(name = "Convert", description = "Convert APIs")
|
||||
@RequestMapping("/api/v1/convert")
|
||||
public class ConvertPDFToHtml {
|
||||
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/pdf/html")
|
||||
@Operation(
|
||||
summary = "Convert PDF to HTML",
|
||||
description =
|
||||
"This endpoint converts a PDF file to HTML format. Input:PDF Output:HTML Type:SISO")
|
||||
public ResponseEntity<byte[]> processPdfToHTML(@ModelAttribute PDFFile request)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
PDFToFile pdfToFile = new PDFToFile();
|
||||
return pdfToFile.processPdfToHtml(inputFile);
|
||||
}
|
||||
}
|
|
@ -16,7 +16,7 @@ import io.github.pixee.security.Filenames;
|
|||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.model.api.converters.PdfToPdfARequest;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor;
|
||||
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
@ -31,8 +31,10 @@ public class ConvertPDFToPDFA {
|
|||
summary = "Convert a PDF to a PDF/A",
|
||||
description =
|
||||
"This endpoint converts a PDF file to a PDF/A file. PDF/A is a format designed for long-term archiving of digital documents. Input:PDF Output:PDF Type:SISO")
|
||||
public ResponseEntity<byte[]> pdfToPdfA(@ModelAttribute PDFFile request) throws Exception {
|
||||
public ResponseEntity<byte[]> pdfToPdfA(@ModelAttribute PdfToPdfARequest request)
|
||||
throws Exception {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
String outputFormat = request.getOutputFormat();
|
||||
|
||||
// Save the uploaded file to a temporary location
|
||||
Path tempInputFile = Files.createTempFile("input_", ".pdf");
|
||||
|
@ -47,7 +49,7 @@ public class ConvertPDFToPDFA {
|
|||
command.add("--skip-text");
|
||||
command.add("--tesseract-timeout=0");
|
||||
command.add("--output-type");
|
||||
command.add("pdfa");
|
||||
command.add(outputFormat.toString());
|
||||
command.add(tempInputFile.toString());
|
||||
command.add(tempOutputFile.toString());
|
||||
|
||||
|
|
|
@ -1,27 +1,29 @@
|
|||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.BasicStroke;
|
||||
import java.awt.Color;
|
||||
import java.awt.GradientPaint;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Ellipse2D;
|
||||
import java.awt.geom.Path2D;
|
||||
import java.awt.image.AffineTransformOp;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.BufferedImageOp;
|
||||
import java.awt.image.ConvolveOp;
|
||||
import java.awt.image.Kernel;
|
||||
import java.awt.image.RescaleOp;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.LosslessFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
|
||||
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
|
||||
import org.apache.pdfbox.rendering.ImageType;
|
||||
import org.apache.pdfbox.rendering.PDFRenderer;
|
||||
|
@ -29,6 +31,7 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
@ -39,6 +42,7 @@ import io.swagger.v3.oas.annotations.Operation;
|
|||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
import stirling.software.SPDF.utils.PdfUtils;
|
||||
import stirling.software.SPDF.utils.WebResponseUtils;
|
||||
|
||||
@RestController
|
||||
|
@ -48,98 +52,39 @@ public class FakeScanControllerWIP {
|
|||
|
||||
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
|
||||
|
||||
// TODO
|
||||
// TODO finish
|
||||
@PostMapping(consumes = "multipart/form-data", value = "/fake-scan")
|
||||
@Hidden
|
||||
// @PostMapping(consumes = "multipart/form-data", value = "/fakeScan")
|
||||
@Operation(
|
||||
summary = "Repair a PDF file",
|
||||
description =
|
||||
"This endpoint repairs a given PDF file by running Ghostscript command. The PDF is first saved to a temporary location, repaired, read back, and then returned as a response.")
|
||||
public ResponseEntity<byte[]> repairPdf(@ModelAttribute PDFFile request) throws IOException {
|
||||
public ResponseEntity<byte[]> fakeScan(@ModelAttribute PDFFile request) throws IOException {
|
||||
MultipartFile inputFile = request.getFileInput();
|
||||
|
||||
// Load the PDF document
|
||||
PDDocument document = Loader.loadPDF(inputFile.getBytes());
|
||||
PDFRenderer pdfRenderer = new PDFRenderer(document);
|
||||
pdfRenderer.setSubsamplingAllowed(true);
|
||||
for (int page = 0; page < document.getNumberOfPages(); ++page) {
|
||||
BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB);
|
||||
ImageIO.write(image, "png", new File("scanned-" + (page + 1) + ".png"));
|
||||
PDFRenderer renderer = new PDFRenderer(document);
|
||||
List<BufferedImage> images = new ArrayList<>();
|
||||
// Convert each page to an image
|
||||
for (int i = 0; i < document.getNumberOfPages(); i++) {
|
||||
BufferedImage image = renderer.renderImageWithDPI(i, 150, ImageType.GRAY);
|
||||
images.add(processImage(image));
|
||||
}
|
||||
document.close();
|
||||
|
||||
// Constants
|
||||
int scannedness = 90; // Value between 0 and 100
|
||||
int dirtiness = 0; // Value between 0 and 100
|
||||
|
||||
// Load the source image
|
||||
BufferedImage sourceImage = ImageIO.read(new File("scanned-1.png"));
|
||||
|
||||
// Create the destination image
|
||||
BufferedImage destinationImage =
|
||||
new BufferedImage(
|
||||
sourceImage.getWidth(), sourceImage.getHeight(), sourceImage.getType());
|
||||
|
||||
// Apply a brightness and contrast effect based on the "scanned-ness"
|
||||
float scaleFactor = 1.0f + (scannedness / 100.0f) * 0.5f; // Between 1.0 and 1.5
|
||||
float offset = scannedness * 1.5f; // Between 0 and 150
|
||||
BufferedImageOp op = new RescaleOp(scaleFactor, offset, null);
|
||||
op.filter(sourceImage, destinationImage);
|
||||
|
||||
// Apply a rotation effect
|
||||
double rotationRequired =
|
||||
Math.toRadians(
|
||||
(new SecureRandom().nextInt(3 - 1)
|
||||
+ 1)); // Random angle between 1 and 3 degrees
|
||||
double locationX = destinationImage.getWidth() / 2;
|
||||
double locationY = destinationImage.getHeight() / 2;
|
||||
AffineTransform tx =
|
||||
AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
|
||||
AffineTransformOp rotateOp = new AffineTransformOp(tx, AffineTransformOp.TYPE_BILINEAR);
|
||||
destinationImage = rotateOp.filter(destinationImage, null);
|
||||
|
||||
// Apply a blur effect based on the "scanned-ness"
|
||||
float blurIntensity = scannedness / 100.0f * 0.2f; // Between 0.0 and 0.2
|
||||
float[] matrix = {
|
||||
blurIntensity, blurIntensity, blurIntensity,
|
||||
blurIntensity, blurIntensity, blurIntensity,
|
||||
blurIntensity, blurIntensity, blurIntensity
|
||||
};
|
||||
BufferedImageOp blurOp =
|
||||
new ConvolveOp(new Kernel(3, 3, matrix), ConvolveOp.EDGE_NO_OP, null);
|
||||
destinationImage = blurOp.filter(destinationImage, null);
|
||||
|
||||
// Add noise to the image based on the "dirtiness"
|
||||
Random random = new SecureRandom();
|
||||
for (int y = 0; y < destinationImage.getHeight(); y++) {
|
||||
for (int x = 0; x < destinationImage.getWidth(); x++) {
|
||||
if (random.nextInt(100) < dirtiness) {
|
||||
// Change the pixel color to black randomly based on the "dirtiness"
|
||||
destinationImage.setRGB(x, y, Color.BLACK.getRGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Save the image
|
||||
ImageIO.write(destinationImage, "PNG", new File("scanned-1.png"));
|
||||
|
||||
PDDocument documentOut = new PDDocument();
|
||||
for (int page = 1; page <= document.getNumberOfPages(); ++page) {
|
||||
BufferedImage bim = ImageIO.read(new File("scanned-" + page + ".png"));
|
||||
|
||||
// Adjust the dimensions of the page
|
||||
PDPage pdPage = new PDPage(new PDRectangle(bim.getWidth() - 1, bim.getHeight() - 1));
|
||||
documentOut.addPage(pdPage);
|
||||
|
||||
PDImageXObject pdImage = LosslessFactory.createFromImage(documentOut, bim);
|
||||
PDPageContentStream contentStream = new PDPageContentStream(documentOut, pdPage);
|
||||
|
||||
// Draw the image with a slight offset and enlarged dimensions
|
||||
contentStream.drawImage(pdImage, -1, -1, bim.getWidth() + 2, bim.getHeight() + 2);
|
||||
contentStream.close();
|
||||
}
|
||||
// Create a new PDF document with the processed images
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
documentOut.save(baos);
|
||||
documentOut.close();
|
||||
PDDocument newDocument = new PDDocument();
|
||||
for (BufferedImage img : images) {
|
||||
// PDPageContentStream contentStream = new PDPageContentStream(newDocument, new
|
||||
// PDPage());
|
||||
PDImageXObject pdImage = JPEGFactory.createFromImage(newDocument, img);
|
||||
PdfUtils.addImageToDocument(newDocument, pdImage, "maintainAspectRatio", false);
|
||||
}
|
||||
|
||||
newDocument.save(baos);
|
||||
newDocument.close();
|
||||
|
||||
// Return the optimized PDF as a response
|
||||
String outputFilename =
|
||||
|
@ -148,4 +93,232 @@ public class FakeScanControllerWIP {
|
|||
+ "_scanned.pdf";
|
||||
return WebResponseUtils.boasToWebResponse(baos, outputFilename);
|
||||
}
|
||||
|
||||
public BufferedImage processImage(BufferedImage image) {
|
||||
// Rotation
|
||||
|
||||
image = softenEdges(image, 50);
|
||||
image = rotate(image, 1);
|
||||
|
||||
image = applyGaussianBlur(image, 0.5);
|
||||
addGaussianNoise(image, 0.5);
|
||||
image = linearStretch(image);
|
||||
addDustAndHairs(image, 3);
|
||||
return image;
|
||||
}
|
||||
|
||||
private BufferedImage rotate(BufferedImage image, double rotation) {
|
||||
|
||||
double rotationRequired = Math.toRadians(rotation);
|
||||
double locationX = image.getWidth() / 2;
|
||||
double locationY = image.getHeight() / 2;
|
||||
AffineTransform tx =
|
||||
AffineTransform.getRotateInstance(rotationRequired, locationX, locationY);
|
||||
AffineTransformOp op = new AffineTransformOp(tx, AffineTransformOp.TYPE_BICUBIC);
|
||||
return op.filter(image, null);
|
||||
}
|
||||
|
||||
private BufferedImage applyGaussianBlur(BufferedImage image, double sigma) {
|
||||
int radius = 3; // Fixed radius size for simplicity
|
||||
|
||||
int size = 2 * radius + 1;
|
||||
float[] data = new float[size * size];
|
||||
double sum = 0.0;
|
||||
|
||||
for (int i = -radius; i <= radius; i++) {
|
||||
for (int j = -radius; j <= radius; j++) {
|
||||
double xDistance = i * i;
|
||||
double yDistance = j * j;
|
||||
double g = Math.exp(-(xDistance + yDistance) / (2 * sigma * sigma));
|
||||
data[(i + radius) * size + j + radius] = (float) g;
|
||||
sum += g;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize the kernel
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
data[i] /= sum;
|
||||
}
|
||||
|
||||
Kernel kernel = new Kernel(size, size, data);
|
||||
BufferedImageOp op = new ConvolveOp(kernel, ConvolveOp.EDGE_NO_OP, null);
|
||||
return op.filter(image, null);
|
||||
}
|
||||
|
||||
public BufferedImage softenEdges(BufferedImage image, int featherRadius) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
BufferedImage output = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
|
||||
|
||||
Graphics2D g2 = output.createGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
|
||||
g2.setRenderingHint(
|
||||
RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
|
||||
|
||||
g2.drawImage(image, 0, 0, null);
|
||||
g2.setComposite(AlphaComposite.DstIn);
|
||||
|
||||
// Top edge
|
||||
g2.setPaint(
|
||||
new GradientPaint(
|
||||
0,
|
||||
0,
|
||||
new Color(0, 0, 0, 1f),
|
||||
0,
|
||||
featherRadius * 2,
|
||||
new Color(0, 0, 0, 0f)));
|
||||
g2.fillRect(0, 0, width, featherRadius);
|
||||
|
||||
// Bottom edge
|
||||
g2.setPaint(
|
||||
new GradientPaint(
|
||||
0,
|
||||
height - featherRadius * 2,
|
||||
new Color(0, 0, 0, 0f),
|
||||
0,
|
||||
height,
|
||||
new Color(0, 0, 0, 1f)));
|
||||
g2.fillRect(0, height - featherRadius, width, featherRadius);
|
||||
|
||||
// Left edge
|
||||
g2.setPaint(
|
||||
new GradientPaint(
|
||||
0,
|
||||
0,
|
||||
new Color(0, 0, 0, 1f),
|
||||
featherRadius * 2,
|
||||
0,
|
||||
new Color(0, 0, 0, 0f)));
|
||||
g2.fillRect(0, 0, featherRadius, height);
|
||||
|
||||
// Right edge
|
||||
g2.setPaint(
|
||||
new GradientPaint(
|
||||
width - featherRadius * 2,
|
||||
0,
|
||||
new Color(0, 0, 0, 0f),
|
||||
width,
|
||||
0,
|
||||
new Color(0, 0, 0, 1f)));
|
||||
g2.fillRect(width - featherRadius, 0, featherRadius, height);
|
||||
|
||||
g2.dispose();
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
private void addDustAndHairs(BufferedImage image, float intensity) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
Graphics2D g2d = image.createGraphics();
|
||||
Random random = new SecureRandom();
|
||||
|
||||
// Set rendering hints for better quality
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
|
||||
// Calculate the number of artifacts based on intensity
|
||||
int numSpots = (int) (intensity * 10);
|
||||
int numHairs = (int) (intensity * 20);
|
||||
|
||||
// Add spots with more variable sizes
|
||||
g2d.setColor(new Color(100, 100, 100, 50)); // Semi-transparent gray
|
||||
for (int i = 0; i < numSpots; i++) {
|
||||
int x = random.nextInt(width);
|
||||
int y = random.nextInt(height);
|
||||
int ovalSize = 1 + random.nextInt(3); // Base size + variable component
|
||||
if (random.nextFloat() > 0.9) {
|
||||
// 10% chance to get a larger spot
|
||||
ovalSize += random.nextInt(3);
|
||||
}
|
||||
g2d.fill(new Ellipse2D.Double(x, y, ovalSize, ovalSize));
|
||||
}
|
||||
|
||||
// Add hairs
|
||||
g2d.setStroke(new BasicStroke(0.5f)); // Thin stroke for hairs
|
||||
g2d.setColor(new Color(80, 80, 80, 40)); // Slightly lighter and more transparent
|
||||
for (int i = 0; i < numHairs; i++) {
|
||||
int x1 = random.nextInt(width);
|
||||
int y1 = random.nextInt(height);
|
||||
int x2 = x1 + random.nextInt(20) - 10; // Random length and direction
|
||||
int y2 = y1 + random.nextInt(20) - 10;
|
||||
Path2D.Double hair = new Path2D.Double();
|
||||
hair.moveTo(x1, y1);
|
||||
hair.curveTo(x1, y1, (x1 + x2) / 2, (y1 + y2) / 2, x2, y2);
|
||||
g2d.draw(hair);
|
||||
}
|
||||
|
||||
g2d.dispose();
|
||||
}
|
||||
|
||||
private void addGaussianNoise(BufferedImage image, double strength) {
|
||||
Random rand = new SecureRandom();
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
for (int i = 0; i < width; i++) {
|
||||
for (int j = 0; j < height; j++) {
|
||||
int rgba = image.getRGB(i, j);
|
||||
int alpha = (rgba >> 24) & 0xff;
|
||||
int red = (rgba >> 16) & 0xff;
|
||||
int green = (rgba >> 8) & 0xff;
|
||||
int blue = rgba & 0xff;
|
||||
|
||||
// Apply Gaussian noise
|
||||
red = (int) (red + rand.nextGaussian() * strength);
|
||||
green = (int) (green + rand.nextGaussian() * strength);
|
||||
blue = (int) (blue + rand.nextGaussian() * strength);
|
||||
|
||||
// Clamping values to the 0-255 range
|
||||
red = Math.min(Math.max(0, red), 255);
|
||||
green = Math.min(Math.max(0, green), 255);
|
||||
blue = Math.min(Math.max(0, blue), 255);
|
||||
|
||||
image.setRGB(i, j, (alpha << 24) | (red << 16) | (green << 8) | blue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BufferedImage linearStretch(BufferedImage image) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
int min = 255;
|
||||
int max = 0;
|
||||
|
||||
// First pass: find the min and max grayscale values
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int rgb = image.getRGB(x, y);
|
||||
int gray =
|
||||
(int)
|
||||
(((rgb >> 16) & 0xff) * 0.299
|
||||
+ ((rgb >> 8) & 0xff) * 0.587
|
||||
+ (rgb & 0xff) * 0.114); // Convert to grayscale
|
||||
if (gray < min) min = gray;
|
||||
if (gray > max) max = gray;
|
||||
}
|
||||
}
|
||||
|
||||
// Second pass: stretch the histogram
|
||||
for (int y = 0; y < height; y++) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
int rgb = image.getRGB(x, y);
|
||||
int alpha = (rgb >> 24) & 0xff;
|
||||
int red = (rgb >> 16) & 0xff;
|
||||
int green = (rgb >> 8) & 0xff;
|
||||
int blue = rgb & 0xff;
|
||||
|
||||
// Apply linear stretch to each channel
|
||||
red = (int) (((red - min) / (float) (max - min)) * 255);
|
||||
green = (int) (((green - min) / (float) (max - min)) * 255);
|
||||
blue = (int) (((blue - min) / (float) (max - min)) * 255);
|
||||
|
||||
// Set new RGB value maintaining the alpha channel
|
||||
rgb = (alpha << 24) | (red << 16) | (green << 8) | blue;
|
||||
image.setRGB(x, y, rgb);
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
package stirling.software.SPDF.controller.api.misc;
|
||||
|
||||
import java.awt.Graphics;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.print.PageFormat;
|
||||
import java.awt.print.Printable;
|
||||
import java.awt.print.PrinterException;
|
||||
import java.awt.print.PrinterJob;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import javax.print.PrintService;
|
||||
import javax.print.PrintServiceLookup;
|
||||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.printing.PDFPageable;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import stirling.software.SPDF.model.api.misc.PrintFileRequest;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/v1/misc")
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class PrintFileController {
|
||||
|
||||
// TODO
|
||||
// @PostMapping(value = "/print-file", consumes = "multipart/form-data")
|
||||
// @Operation(
|
||||
// summary = "Prints PDF/Image file to a set printer",
|
||||
// description =
|
||||
// "Input of PDF or Image along with a printer name/URL/IP to match against to
|
||||
// send it to (Fire and forget) Input:Any Output:N/A Type:SISO")
|
||||
public ResponseEntity<String> printFile(@ModelAttribute PrintFileRequest request)
|
||||
throws IOException {
|
||||
MultipartFile file = request.getFileInput();
|
||||
String printerName = request.getPrinterName();
|
||||
String contentType = file.getContentType();
|
||||
try {
|
||||
// Find matching printer
|
||||
PrintService[] services = PrintServiceLookup.lookupPrintServices(null, null);
|
||||
PrintService selectedService =
|
||||
Arrays.stream(services)
|
||||
.filter(
|
||||
service ->
|
||||
service.getName().toLowerCase().contains(printerName))
|
||||
.findFirst()
|
||||
.orElseThrow(
|
||||
() ->
|
||||
new IllegalArgumentException(
|
||||
"No matching printer found"));
|
||||
|
||||
System.out.println("Selected Printer: " + selectedService.getName());
|
||||
|
||||
if ("application/pdf".equals(contentType)) {
|
||||
PDDocument document = Loader.loadPDF(file.getBytes());
|
||||
PrinterJob job = PrinterJob.getPrinterJob();
|
||||
job.setPrintService(selectedService);
|
||||
job.setPageable(new PDFPageable(document));
|
||||
job.print();
|
||||
document.close();
|
||||
} else if (contentType.startsWith("image/")) {
|
||||
BufferedImage image = ImageIO.read(file.getInputStream());
|
||||
PrinterJob job = PrinterJob.getPrinterJob();
|
||||
job.setPrintService(selectedService);
|
||||
job.setPrintable(
|
||||
new Printable() {
|
||||
public int print(
|
||||
Graphics graphics, PageFormat pageFormat, int pageIndex)
|
||||
throws PrinterException {
|
||||
if (pageIndex != 0) {
|
||||
return NO_SUCH_PAGE;
|
||||
}
|
||||
Graphics2D g2d = (Graphics2D) graphics;
|
||||
g2d.translate(
|
||||
pageFormat.getImageableX(), pageFormat.getImageableY());
|
||||
g2d.drawImage(
|
||||
image,
|
||||
0,
|
||||
0,
|
||||
(int) pageFormat.getImageableWidth(),
|
||||
(int) pageFormat.getImageableHeight(),
|
||||
null);
|
||||
return PAGE_EXISTS;
|
||||
}
|
||||
});
|
||||
job.print();
|
||||
}
|
||||
return new ResponseEntity<>(
|
||||
"File printed successfully to " + selectedService.getName(), HttpStatus.OK);
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to print: " + e.getMessage());
|
||||
return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -139,25 +139,29 @@ public class SanitizeController {
|
|||
|
||||
for (PDPage page : allPages) {
|
||||
PDResources res = page.getResources();
|
||||
|
||||
// Remove embedded files from the PDF
|
||||
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
|
||||
if (res != null && res.getCOSObject() != null) {
|
||||
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sanitizeMetadata(PDDocument document) {
|
||||
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
|
||||
if (metadata != null) {
|
||||
document.getDocumentCatalog().setMetadata(null);
|
||||
if (document.getDocumentCatalog() != null) {
|
||||
PDMetadata metadata = document.getDocumentCatalog().getMetadata();
|
||||
if (metadata != null) {
|
||||
document.getDocumentCatalog().setMetadata(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sanitizeLinks(PDDocument document) throws IOException {
|
||||
for (PDPage page : document.getPages()) {
|
||||
for (PDAnnotation annotation : page.getAnnotations()) {
|
||||
if (annotation instanceof PDAnnotationLink) {
|
||||
if (annotation != null && annotation instanceof PDAnnotationLink) {
|
||||
PDAction action = ((PDAnnotationLink) annotation).getAction();
|
||||
if (action instanceof PDActionLaunch || action instanceof PDActionURI) {
|
||||
if (action != null
|
||||
&& (action instanceof PDActionLaunch
|
||||
|| action instanceof PDActionURI)) {
|
||||
((PDAnnotationLink) annotation).setAction(null);
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +171,11 @@ public class SanitizeController {
|
|||
|
||||
private void sanitizeFonts(PDDocument document) {
|
||||
for (PDPage page : document.getPages()) {
|
||||
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
|
||||
if (page != null
|
||||
&& page.getResources() != null
|
||||
&& page.getResources().getCOSObject() != null) {
|
||||
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
@ -19,6 +20,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
|||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import stirling.software.SPDF.model.ApplicationProperties;
|
||||
import stirling.software.SPDF.model.Authority;
|
||||
import stirling.software.SPDF.model.Role;
|
||||
import stirling.software.SPDF.model.User;
|
||||
|
@ -28,12 +30,17 @@ import stirling.software.SPDF.repository.UserRepository;
|
|||
@Tag(name = "Account Security", description = "Account Security APIs")
|
||||
public class AccountWebController {
|
||||
|
||||
@Autowired ApplicationProperties applicationProperties;
|
||||
|
||||
@GetMapping("/login")
|
||||
public String login(HttpServletRequest request, Model model, Authentication authentication) {
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
return "redirect:/";
|
||||
}
|
||||
|
||||
model.addAttribute(
|
||||
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
|
||||
|
||||
model.addAttribute("currentPage", "login");
|
||||
|
||||
if (request.getParameter("error") != null) {
|
||||
|
@ -85,17 +92,32 @@ public class AccountWebController {
|
|||
}
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
Object principal = authentication.getPrincipal();
|
||||
String username = null;
|
||||
|
||||
if (principal instanceof UserDetails) {
|
||||
// Cast the principal object to UserDetails
|
||||
UserDetails userDetails = (UserDetails) principal;
|
||||
|
||||
// Retrieve username and other attributes
|
||||
String username = userDetails.getUsername();
|
||||
username = userDetails.getUsername();
|
||||
|
||||
// Add oAuth2 Login attributes to the model
|
||||
model.addAttribute("oAuth2Login", false);
|
||||
}
|
||||
if (principal instanceof OAuth2User) {
|
||||
// Cast the principal object to OAuth2User
|
||||
OAuth2User userDetails = (OAuth2User) principal;
|
||||
|
||||
// Retrieve username and other attributes
|
||||
username = userDetails.getAttribute("email");
|
||||
|
||||
// Add oAuth2 Login attributes to the model
|
||||
model.addAttribute("oAuth2Login", true);
|
||||
}
|
||||
if (username != null) {
|
||||
// Fetch user details from the database
|
||||
Optional<User> user =
|
||||
userRepository.findByUsername(
|
||||
userRepository.findByUsernameIgnoreCase(
|
||||
username); // Assuming findByUsername method exists
|
||||
if (!user.isPresent()) {
|
||||
// Handle error appropriately
|
||||
|
@ -145,7 +167,7 @@ public class AccountWebController {
|
|||
|
||||
// Fetch user details from the database
|
||||
Optional<User> user =
|
||||
userRepository.findByUsername(
|
||||
userRepository.findByUsernameIgnoreCase(
|
||||
username); // Assuming findByUsername method exists
|
||||
if (!user.isPresent()) {
|
||||
// Handle error appropriately
|
||||
|
|
|
@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||
@Controller
|
||||
@Tag(name = "Misc", description = "Miscellaneous APIs")
|
||||
public class OtherWebController {
|
||||
|
||||
@GetMapping("/compress-pdf")
|
||||
@Hidden
|
||||
public String compressPdfForm(Model model) {
|
||||
|
@ -53,6 +54,13 @@ public class OtherWebController {
|
|||
return "misc/add-page-numbers";
|
||||
}
|
||||
|
||||
@GetMapping("/fake-scan")
|
||||
@Hidden
|
||||
public String fakeScanForm(Model model) {
|
||||
model.addAttribute("currentPage", "fake-scan");
|
||||
return "misc/fake-scan";
|
||||
}
|
||||
|
||||
@GetMapping("/extract-images")
|
||||
@Hidden
|
||||
public String extractImagesForm(Model model) {
|
||||
|
@ -81,6 +89,13 @@ public class OtherWebController {
|
|||
return "misc/compare";
|
||||
}
|
||||
|
||||
@GetMapping("/print-file")
|
||||
@Hidden
|
||||
public String printFileForm(Model model) {
|
||||
model.addAttribute("currentPage", "print-file");
|
||||
return "misc/print-file";
|
||||
}
|
||||
|
||||
public List<String> getAvailableTesseractLanguages() {
|
||||
String tessdataDir = "/usr/share/tessdata";
|
||||
File[] files = new File(tessdataDir).listFiles();
|
||||
|
|
|
@ -118,6 +118,7 @@ public class ApplicationProperties {
|
|||
private Boolean enableLogin;
|
||||
private Boolean csrfDisabled;
|
||||
private InitialLogin initialLogin;
|
||||
private OAUTH2 oauth2;
|
||||
private int loginAttemptCount;
|
||||
private long loginResetTimeMinutes;
|
||||
|
||||
|
@ -145,6 +146,14 @@ public class ApplicationProperties {
|
|||
this.initialLogin = initialLogin;
|
||||
}
|
||||
|
||||
public OAUTH2 getOAUTH2() {
|
||||
return oauth2 != null ? oauth2 : new OAUTH2();
|
||||
}
|
||||
|
||||
public void setOAUTH2(OAUTH2 oauth2) {
|
||||
this.oauth2 = oauth2;
|
||||
}
|
||||
|
||||
public Boolean getEnableLogin() {
|
||||
return enableLogin;
|
||||
}
|
||||
|
@ -165,6 +174,8 @@ public class ApplicationProperties {
|
|||
public String toString() {
|
||||
return "Security [enableLogin="
|
||||
+ enableLogin
|
||||
+ ", oauth2="
|
||||
+ oauth2
|
||||
+ ", initialLogin="
|
||||
+ initialLogin
|
||||
+ ", csrfDisabled="
|
||||
|
@ -202,6 +213,70 @@ public class ApplicationProperties {
|
|||
+ "]";
|
||||
}
|
||||
}
|
||||
|
||||
public static class OAUTH2 {
|
||||
|
||||
private boolean enabled;
|
||||
private String issuer;
|
||||
private String clientId;
|
||||
private String clientSecret;
|
||||
private boolean autoCreateUser;
|
||||
|
||||
public boolean getEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public void setEnabled(boolean enabled) {
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
||||
public String getIssuer() {
|
||||
return issuer;
|
||||
}
|
||||
|
||||
public void setIssuer(String issuer) {
|
||||
this.issuer = issuer;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
}
|
||||
|
||||
public String getClientSecret() {
|
||||
return clientSecret;
|
||||
}
|
||||
|
||||
public void setClientSecret(String clientSecret) {
|
||||
this.clientSecret = clientSecret;
|
||||
}
|
||||
|
||||
public boolean getAutoCreateUser() {
|
||||
return autoCreateUser;
|
||||
}
|
||||
|
||||
public void setAutoCreateUser(boolean autoCreateUser) {
|
||||
this.autoCreateUser = autoCreateUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OAUTH2 [enabled="
|
||||
+ enabled
|
||||
+ ", issuer="
|
||||
+ issuer
|
||||
+ ", clientId="
|
||||
+ clientId
|
||||
+ ", clientSecret="
|
||||
+ (clientSecret != null && !clientSecret.isEmpty() ? "MASKED" : "NULL")
|
||||
+ ", autoCreateUser="
|
||||
+ autoCreateUser
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class System {
|
||||
|
@ -210,6 +285,33 @@ public class ApplicationProperties {
|
|||
private String rootURIPath;
|
||||
private String customStaticFilePath;
|
||||
private Integer maxFileSize;
|
||||
private boolean showUpdate;
|
||||
private Boolean showUpdateOnlyAdmin;
|
||||
private boolean customHTMLFiles;
|
||||
|
||||
public boolean isCustomHTMLFiles() {
|
||||
return customHTMLFiles;
|
||||
}
|
||||
|
||||
public void setCustomHTMLFiles(boolean customHTMLFiles) {
|
||||
this.customHTMLFiles = customHTMLFiles;
|
||||
}
|
||||
|
||||
public boolean getShowUpdateOnlyAdmin() {
|
||||
return showUpdateOnlyAdmin;
|
||||
}
|
||||
|
||||
public void setShowUpdateOnlyAdmin(boolean showUpdateOnlyAdmin) {
|
||||
this.showUpdateOnlyAdmin = showUpdateOnlyAdmin;
|
||||
}
|
||||
|
||||
public boolean getShowUpdate() {
|
||||
return showUpdate;
|
||||
}
|
||||
|
||||
public void setShowUpdate(boolean showUpdate) {
|
||||
this.showUpdate = showUpdate;
|
||||
}
|
||||
|
||||
private Boolean enableAlphaFunctionality;
|
||||
|
||||
|
@ -275,6 +377,10 @@ public class ApplicationProperties {
|
|||
+ maxFileSize
|
||||
+ ", enableAlphaFunctionality="
|
||||
+ enableAlphaFunctionality
|
||||
+ ", showUpdate="
|
||||
+ showUpdate
|
||||
+ ", showUpdateOnlyAdmin="
|
||||
+ showUpdateOnlyAdmin
|
||||
+ "]";
|
||||
}
|
||||
}
|
||||
|
|
19
src/main/java/stirling/software/SPDF/model/PdfMetadata.java
Normal file
19
src/main/java/stirling/software/SPDF/model/PdfMetadata.java
Normal file
|
@ -0,0 +1,19 @@
|
|||
package stirling.software.SPDF.model;
|
||||
|
||||
import java.util.Calendar;
|
||||
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Builder
|
||||
public class PdfMetadata {
|
||||
private String author;
|
||||
private String producer;
|
||||
private String title;
|
||||
private String creator;
|
||||
private String subject;
|
||||
private String keywords;
|
||||
private Calendar creationDate;
|
||||
private Calendar modificationDate;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package stirling.software.SPDF.model.api.converters;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PdfToPdfARequest extends PDFFile {
|
||||
|
||||
@Schema(
|
||||
description = "The output PDF/A type",
|
||||
allowableValues = {"pdfa", "pdfa-1"})
|
||||
private String outputFormat;
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package stirling.software.SPDF.model.api.misc;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import stirling.software.SPDF.model.api.PDFFile;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class PrintFileRequest extends PDFFile {
|
||||
|
||||
@Schema(description = "Name of printer to match against", required = true)
|
||||
private String printerName;
|
||||
}
|
|
@ -9,4 +9,6 @@ import stirling.software.SPDF.model.Authority;
|
|||
public interface AuthorityRepository extends JpaRepository<Authority, Long> {
|
||||
// Set<Authority> findByUsername(String username);
|
||||
Set<Authority> findByUser_Username(String username);
|
||||
|
||||
Authority findByUserId(long user_id);
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ public class GeneralUtils {
|
|||
}
|
||||
|
||||
sizeStr = sizeStr.trim().toUpperCase();
|
||||
sizeStr = sizeStr.replace(",", ".").replace(" ", "");
|
||||
try {
|
||||
if (sizeStr.endsWith("KB")) {
|
||||
return (long)
|
||||
|
@ -185,7 +186,7 @@ public class GeneralUtils {
|
|||
// Insert multiplication between a number and 'n' (e.g., "4n" becomes "4*n")
|
||||
String withMultiplication = expression.replaceAll("(\\d)n", "$1*n");
|
||||
// Now replace 'n' with its current value
|
||||
return withMultiplication.replaceAll("n", String.valueOf(nValue));
|
||||
return withMultiplication.replace("n", String.valueOf(nValue));
|
||||
}
|
||||
|
||||
private static List<Integer> handlePart(String part, int totalPages, int offset) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import java.awt.image.RenderedImage;
|
|||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
@ -19,11 +20,8 @@ import javax.imageio.stream.ImageOutputStream;
|
|||
|
||||
import org.apache.pdfbox.Loader;
|
||||
import org.apache.pdfbox.cos.COSName;
|
||||
import org.apache.pdfbox.pdmodel.PDDocument;
|
||||
import org.apache.pdfbox.pdmodel.PDPage;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream;
|
||||
import org.apache.pdfbox.pdmodel.*;
|
||||
import org.apache.pdfbox.pdmodel.PDPageContentStream.AppendMode;
|
||||
import org.apache.pdfbox.pdmodel.PDResources;
|
||||
import org.apache.pdfbox.pdmodel.common.PDRectangle;
|
||||
import org.apache.pdfbox.pdmodel.graphics.PDXObject;
|
||||
import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject;
|
||||
|
@ -39,6 +37,8 @@ import org.springframework.web.multipart.MultipartFile;
|
|||
|
||||
import io.github.pixee.security.Filenames;
|
||||
|
||||
import stirling.software.SPDF.model.PdfMetadata;
|
||||
|
||||
public class PdfUtils {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);
|
||||
|
@ -336,14 +336,12 @@ public class PdfUtils {
|
|||
}
|
||||
}
|
||||
|
||||
private static void addImageToDocument(
|
||||
public static void addImageToDocument(
|
||||
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate)
|
||||
throws IOException {
|
||||
boolean imageIsLandscape = image.getWidth() > image.getHeight();
|
||||
PDRectangle pageSize = PDRectangle.A4;
|
||||
|
||||
System.out.println(fitOption);
|
||||
|
||||
if (autoRotate && imageIsLandscape) {
|
||||
pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth());
|
||||
}
|
||||
|
@ -421,4 +419,28 @@ public class PdfUtils {
|
|||
logger.info("PDF successfully saved to byte array");
|
||||
return baos.toByteArray();
|
||||
}
|
||||
|
||||
public static PdfMetadata extractMetadataFromPdf(PDDocument pdf) {
|
||||
return PdfMetadata.builder()
|
||||
.author(pdf.getDocumentInformation().getAuthor())
|
||||
.producer(pdf.getDocumentInformation().getProducer())
|
||||
.title(pdf.getDocumentInformation().getTitle())
|
||||
.creator(pdf.getDocumentInformation().getCreator())
|
||||
.subject(pdf.getDocumentInformation().getSubject())
|
||||
.keywords(pdf.getDocumentInformation().getKeywords())
|
||||
.creationDate(pdf.getDocumentInformation().getCreationDate())
|
||||
.modificationDate(pdf.getDocumentInformation().getModificationDate())
|
||||
.build();
|
||||
}
|
||||
|
||||
public static void setMetadataToPdf(PDDocument pdf, PdfMetadata pdfMetadata) {
|
||||
pdf.getDocumentInformation().setAuthor(pdfMetadata.getAuthor());
|
||||
pdf.getDocumentInformation().setProducer(pdfMetadata.getProducer());
|
||||
pdf.getDocumentInformation().setTitle(pdfMetadata.getTitle());
|
||||
pdf.getDocumentInformation().setCreator(pdfMetadata.getCreator());
|
||||
pdf.getDocumentInformation().setSubject(pdfMetadata.getSubject());
|
||||
pdf.getDocumentInformation().setKeywords(pdfMetadata.getKeywords());
|
||||
pdf.getDocumentInformation().setCreationDate(pdfMetadata.getCreationDate());
|
||||
pdf.getDocumentInformation().setModificationDate(Calendar.getInstance());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=لا يمكن خفض دور المستخدم الحالي
|
||||
downgradeCurrentUserLongMessage=لا يمكن تخفيض دور المستخدم الحالي. وبالتالي، لن يظهر المستخدم الحالي.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -116,6 +118,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=الإعدادات
|
||||
settings.update=التحديث متاح
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=إصدار التطبيق:
|
||||
settings.downloadOption.title=تحديد خيار التنزيل (للتنزيلات ذات الملف الواحد غير المضغوط):
|
||||
settings.downloadOption.1=فتح في نفس النافذة
|
||||
|
@ -124,8 +127,9 @@ settings.downloadOption.3=تنزيل الملف
|
|||
settings.zipThreshold=ملفات مضغوطة عند تجاوز عدد الملفات التي تم تنزيلها
|
||||
settings.signOut=Sign Out
|
||||
settings.accountSettings=Account Settings
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -175,6 +179,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=تغيير دور المستخدم
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -439,6 +444,8 @@ login.rememberme=Remember me
|
|||
login.invalid=Invalid username or password.
|
||||
login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
|
||||
login.oauth2AutoCreateDisabled=تم تعطيل مستخدم الإنشاء التلقائي لـ OAuth2
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -944,6 +951,7 @@ pdfToPDFA.header=PDF إلى PDF / A
|
|||
pdfToPDFA.credit=تستخدم هذه الخدمة OCRmyPDF لتحويل PDF / A.
|
||||
pdfToPDFA.submit=تحويل
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1027,6 +1035,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Новият потребител вече съществ
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
|
||||
downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Настройки
|
||||
settings.update=Налична актуализация
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Версия на приложението:
|
||||
settings.downloadOption.title=Изберете опция за изтегляне (за изтегляния на един файл без да е архивиран):
|
||||
settings.downloadOption.1=Отваряне в същия прозорец
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Изтегли файл
|
|||
settings.zipThreshold=Архивирайте файловете, когато броят на изтеглените файлове надвишава
|
||||
settings.signOut=Изход
|
||||
settings.accountSettings=Настройки на акаунта
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Промяна на идентификационните данни
|
||||
changeCreds.header=Актуализирайте данните за акаунта си
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
|
||||
adminUserSettings.submit=Съхранете потребителя
|
||||
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Запомни ме
|
|||
login.invalid=Невалидно потребителско име или парола.
|
||||
login.locked=Вашият акаунт е заключен.
|
||||
login.signinTitle=Моля впишете се
|
||||
login.ssoSignIn=Влизане чрез еднократно влизане
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF към PDF/A
|
|||
pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A преобразуване.
|
||||
pdfToPDFA.submit=Преобразуване
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=No es pot reduir la funció de l'usuari actual
|
||||
downgradeCurrentUserLongMessage=No es pot baixar la funció de l'usuari actual. Per tant, no es mostrarà l'usuari actual.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Opcions
|
||||
settings.update=Actualització Disponible
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Versió App:
|
||||
settings.downloadOption.title=Trieu l'opció de descàrrega (per a descàrregues d'un sol fitxer no zip):
|
||||
settings.downloadOption.1=Obre mateixa finestra
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Descarrega Arxiu
|
|||
settings.zipThreshold=Comprimiu els fitxers quan el nombre de fitxers baixats superi
|
||||
settings.signOut=Sortir
|
||||
settings.accountSettings=Account Settings
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Desar Usuari
|
||||
adminUserSettings.changeUserRole=Canvia el rol de l'usuari
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Recordar
|
|||
login.invalid=Nom usuari / password no vàlid
|
||||
login.locked=Compte bloquejat
|
||||
login.signinTitle=Autenticat
|
||||
login.ssoSignIn=Inicia sessió mitjançant l'inici de sessió ún
|
||||
login.oauth2AutoCreateDisabled=L'usuari de creació automàtica OAUTH2 està desactivat
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF a PDF/A
|
|||
pdfToPDFA.credit=Utilitza OCRmyPDF per la conversió a PDF/A
|
||||
pdfToPDFA.submit=Converteix
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -46,7 +46,7 @@ green=Grün
|
|||
blue=Blau
|
||||
custom=benutzerdefiniert...
|
||||
WorkInProgess=In Arbeit, funktioniert möglicherweise nicht oder ist fehlerhaft. Bitte melden Sie alle Probleme!
|
||||
poweredBy=Powered by
|
||||
poweredBy=Unterstützt von
|
||||
yes=Ja
|
||||
no=Nein
|
||||
changedCredsMessage=Anmeldedaten geändert!
|
||||
|
@ -57,15 +57,17 @@ usernameExistsMessage=Neuer Benutzername existiert bereits.
|
|||
invalidUsernameMessage=Ungültiger Benutzername. Der Benutzername darf nur Buchstaben und Zahlen enthalten.
|
||||
deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht werden.
|
||||
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
goHomepage=Go to Homepage
|
||||
joinDiscord=Join our Discord server
|
||||
seeDockerHub=See Docker Hub
|
||||
visitGithub=Visit Github Repository
|
||||
donate=Donate
|
||||
color=Color
|
||||
downgradeCurrentUserMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden
|
||||
downgradeCurrentUserLongMessage=Die Rolle des aktuellen Benutzers kann nicht herabgestuft werden. Daher wird der aktuelle Benutzer nicht angezeigt.
|
||||
error=Fehler
|
||||
oops=Hoppla!
|
||||
help=Hilfe
|
||||
goHomepage=Zur Startseite gehen
|
||||
joinDiscord=Unserem Discord-Server beitreten
|
||||
seeDockerHub=Docker Hub ansehen
|
||||
visitGithub=GitHub-Repository besuchen
|
||||
donate=Spenden
|
||||
color=Farbe
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
|
||||
|
@ -79,8 +81,8 @@ pipeline.uploadButton=Benutzerdefinierter Upload
|
|||
pipeline.configureButton=Konfigurieren
|
||||
pipeline.defaultOption=Benutzerdefiniert
|
||||
pipeline.submitButton=Speichern
|
||||
pipeline.help=Pipeline Help
|
||||
pipeline.scanHelp=Folder Scanning Help
|
||||
pipeline.help=Hilfe für Pipeline
|
||||
pipeline.scanHelp=Hilfe zum Ordnerscan
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Einstellungen
|
||||
settings.update=Update verfügbar
|
||||
settings.updateAvailable={0} ist die aktuelle installierte Version. Eine neue Version ({1}) ist verfügbar.
|
||||
settings.appVersion=App-Version:
|
||||
settings.downloadOption.title=Download-Option wählen (für einzelne Dateien, die keine Zip-Downloads sind):
|
||||
settings.downloadOption.1=Im selben Fenster öffnen
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Datei herunterladen
|
|||
settings.zipThreshold=Dateien komprimieren, wenn die Anzahl der heruntergeladenen Dateien überschritten wird
|
||||
settings.signOut=Abmelden
|
||||
settings.accountSettings=Kontoeinstellungen
|
||||
|
||||
|
||||
settings.bored.help=Aktiviert das Easter-Egg-Spiel
|
||||
settings.cacheInputs.name=Formulareingaben speichern
|
||||
settings.cacheInputs.help=Aktivieren, um zuvor verwendete Eingaben für zukünftige Durchläufe zu speichern
|
||||
|
||||
changeCreds.title=Anmeldeinformationen ändern
|
||||
changeCreds.header=Aktualisieren Sie Ihre Kontodaten
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo-Benutzer (Keine benutzerdefinierten Einstellunge
|
|||
adminUserSettings.internalApiUser=Interner API-Benutzer
|
||||
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
|
||||
adminUserSettings.submit=Benutzer speichern
|
||||
adminUserSettings.changeUserRole=Benutzerrolle ändern
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -193,58 +198,58 @@ home.multiTool.desc=Seiten zusammenführen, drehen, neu anordnen und entfernen
|
|||
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side
|
||||
|
||||
home.merge.title=Zusammenführen
|
||||
home.merge.desc=Mehrere PDF-Dateien zu einer einzigen zusammenführen.
|
||||
home.merge.desc=Mehrere PDF-Dateien zu einer einzigen zusammenführen
|
||||
merge.tags=zusammenführen,seitenvorgänge,back end,serverseite
|
||||
|
||||
home.split.title=Aufteilen
|
||||
home.split.desc=PDFs in mehrere Dokumente aufteilen.
|
||||
home.split.desc=PDFs in mehrere Dokumente aufteilen
|
||||
split.tags=seitenoperationen,teilen,mehrseitig,ausschneiden,serverseitig
|
||||
|
||||
home.rotate.title=Drehen
|
||||
home.rotate.desc=Drehen Sie Ihre PDFs ganz einfach.
|
||||
home.rotate.desc=Drehen Sie Ihre PDFs ganz einfach
|
||||
rotate.tags=serverseitig
|
||||
|
||||
|
||||
home.imageToPdf.title=Bild zu PDF
|
||||
home.imageToPdf.desc=Konvertieren Sie ein Bild (PNG, JPEG, GIF) in ein PDF.
|
||||
home.imageToPdf.desc=Konvertieren Sie ein Bild (PNG, JPEG, GIF) in ein PDF
|
||||
imageToPdf.tags=konvertierung,img,jpg,bild,foto
|
||||
|
||||
home.pdfToImage.title=PDF zu Bild
|
||||
home.pdfToImage.desc=Konvertieren Sie ein PDF in ein Bild (PNG, JPEG, GIF).
|
||||
home.pdfToImage.desc=Konvertieren Sie ein PDF in ein Bild (PNG, JPEG, GIF)
|
||||
pdfToImage.tags=konvertierung,img,jpg,bild,foto
|
||||
|
||||
home.pdfOrganiser.title=Organisieren
|
||||
home.pdfOrganiser.desc=Seiten entfernen und Seitenreihenfolge ändern.
|
||||
home.pdfOrganiser.desc=Seiten entfernen und Seitenreihenfolge ändern
|
||||
pdfOrganiser.tags=duplex,gerade,ungerade,sortieren,verschieben
|
||||
|
||||
|
||||
home.addImage.title=Bild einfügen
|
||||
home.addImage.desc=Fügt ein Bild an eine bestimmte Stelle im PDF ein (in Arbeit).
|
||||
home.addImage.desc=Fügt ein Bild an eine bestimmte Stelle im PDF ein (in Arbeit)
|
||||
addImage.tags=img,jpg,bild,foto
|
||||
|
||||
home.watermark.title=Wasserzeichen hinzufügen
|
||||
home.watermark.desc=Fügen Sie ein eigenes Wasserzeichen zu Ihrem PDF hinzu.
|
||||
home.watermark.desc=Fügen Sie ein eigenes Wasserzeichen zu Ihrem PDF hinzu
|
||||
watermark.tags=text,wiederholend,beschriftung,besitzen,urheberrecht,marke,img,jpg,bild,foto
|
||||
|
||||
home.permissions.title=Berechtigungen ändern
|
||||
home.permissions.desc=Die Berechtigungen für Ihr PDF-Dokument verändern.
|
||||
home.permissions.desc=Die Berechtigungen für Ihr PDF-Dokument verändern
|
||||
permissions.tags=lesen,schreiben,bearbeiten,drucken
|
||||
|
||||
|
||||
home.removePages.title=Entfernen
|
||||
home.removePages.desc=Ungewollte Seiten aus dem PDF entfernen.
|
||||
home.removePages.desc=Ungewollte Seiten aus dem PDF entfernen
|
||||
removePages.tags=seiten entfernen,seiten löschen
|
||||
|
||||
home.addPassword.title=Passwort hinzufügen
|
||||
home.addPassword.desc=Das PDF mit einem Passwort verschlüsseln.
|
||||
home.addPassword.desc=Das PDF mit einem Passwort verschlüsseln
|
||||
addPassword.tags=sicher,sicherheit
|
||||
|
||||
home.removePassword.title=Passwort entfernen
|
||||
home.removePassword.desc=Den Passwortschutz eines PDFs entfernen.
|
||||
home.removePassword.desc=Den Passwortschutz eines PDFs entfernen
|
||||
removePassword.tags=sichern,entschlüsseln,sicherheit,passwort aufheben,passwort löschen
|
||||
|
||||
home.compressPdfs.title=Komprimieren
|
||||
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren.
|
||||
home.compressPdfs.desc=PDF komprimieren um die Dateigröße zu reduzieren
|
||||
compressPdfs.tags=komprimieren,verkleinern,minimieren
|
||||
|
||||
|
||||
|
@ -257,7 +262,7 @@ home.fileToPDF.desc=Konvertieren Sie nahezu jede Datei in PDF (DOCX, PNG, XLS, P
|
|||
fileToPDF.tags=transformation,format,dokument,bild,folie,text,konvertierung,büro,dokumente,word,excel,powerpoint
|
||||
|
||||
home.ocr.title=Führe OCR/Cleanup-Scans aus
|
||||
home.ocr.desc=Cleanup scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu.
|
||||
home.ocr.desc=Cleanup scannt und erkennt Text aus Bildern in einer PDF-Datei und fügt ihn erneut als Text hinzu
|
||||
ocr.tags=erkennung,text,bild,scannen,lesen,identifizieren,erkennung,bearbeitbar
|
||||
|
||||
|
||||
|
@ -379,17 +384,17 @@ getPdfInfo.tags=infomation,daten,statistik
|
|||
|
||||
home.extractPage.title=Seite(n) extrahieren
|
||||
home.extractPage.desc=Extrahiert ausgewählte Seiten aus einer PDF
|
||||
extractPage.tags=extrahieren
|
||||
extractPage.tags=extrahieren,seite
|
||||
|
||||
|
||||
home.PdfToSinglePage.title=PDF zu einer Seite zusammenfassen
|
||||
home.PdfToSinglePage.desc=Fügt alle PDF-Seiten zu einer einzigen großen Seite zusammen
|
||||
PdfToSinglePage.tags=einzelseite
|
||||
PdfToSinglePage.tags=einzelseite,zusammenfassen
|
||||
|
||||
|
||||
home.showJS.title=Javascript anzeigen
|
||||
home.showJS.desc=Alle Javascript Funktionen in einer PDF anzeigen
|
||||
showJS.tags=js
|
||||
showJS.tags=js,javascript
|
||||
|
||||
home.autoRedact.title=Automatisch zensieren/schwärzen
|
||||
home.autoRedact.desc=Automatisches Zensieren (Schwärzen) von Text in einer PDF-Datei basierend auf dem eingegebenen Text
|
||||
|
@ -397,7 +402,7 @@ autoRedact.tags=zensieren,schwärzen
|
|||
|
||||
home.tableExtraxt.title=Tabelle extrahieren
|
||||
home.tableExtraxt.desc=Tabelle aus PDF in CSV extrahieren
|
||||
tableExtraxt.tags=CSV
|
||||
tableExtraxt.tags=CSV,tabelle,extrahieren
|
||||
|
||||
|
||||
home.autoSizeSplitPDF.title=Teilen nach Größe/Anzahl
|
||||
|
@ -420,11 +425,11 @@ AddStampRequest.tags=stempeln,bild hinzufügen,bild zentrieren,wasserzeichen,pdf
|
|||
|
||||
home.PDFToBook.title=PDF zum Buch
|
||||
home.PDFToBook.desc=Konvertiert PDF mit Calibre in Buch-/Comic-Formate
|
||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
PDFToBook.tags=buch,comic,calibre,convert,manga,amazon,kindle
|
||||
|
||||
home.BookToPDF.title=Buch als PDF
|
||||
home.BookToPDF.desc=Konvertiert Buch-/Comic-Formate mithilfe von Calibre in PDF
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
BookToPDF.tags=buch,comic,calibre,convert,manga,amazon,kindle
|
||||
|
||||
|
||||
###########################
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Angemeldet bleiben
|
|||
login.invalid=Benutzername oder Passwort ungültig.
|
||||
login.locked=Ihr Konto wurde gesperrt.
|
||||
login.signinTitle=Bitte melden Sie sich an.
|
||||
login.ssoSignIn=Anmeldung per Single Sign-On
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -450,7 +457,7 @@ autoRedact.textsToRedactLabel=Zu zensierender Text (einer pro Zeile)
|
|||
autoRedact.textsToRedactPlaceholder=z.B. \nVertraulich \nStreng geheim
|
||||
autoRedact.useRegexLabel=Regex verwenden
|
||||
autoRedact.wholeWordSearchLabel=Ganzes Wort suchen
|
||||
autoRedact.customPaddingLabel=Benutzerdefinierte Extra-Padding
|
||||
autoRedact.customPaddingLabel=Zensierten Bereich vergrößern
|
||||
autoRedact.convertPDFToImageLabel=PDF in PDF-Bild konvertieren (zum Entfernen von Text hinter dem Kasten)
|
||||
autoRedact.submitButton=Zensieren
|
||||
|
||||
|
@ -472,7 +479,7 @@ pdfToSinglePage.submit=Zusammenfassen
|
|||
pageExtracter.title=Seiten extrahieren
|
||||
pageExtracter.header=Seiten extrahieren
|
||||
pageExtracter.submit=Extrahieren
|
||||
pageExtracter.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||
pageExtracter.placeholder=(z.B. 1,2,8 oder 4,7,12-16 oder 2n-1)
|
||||
|
||||
|
||||
#getPdfInfo
|
||||
|
@ -504,16 +511,16 @@ HTMLToPDF.header=HTML zu PDF
|
|||
HTMLToPDF.help=Akzeptiert HTML-Dateien und ZIPs mit html/css/images etc.
|
||||
HTMLToPDF.submit=Konvertieren
|
||||
HTMLToPDF.credit=Verwendet WeasyPrint
|
||||
HTMLToPDF.zoom=Zoomstufe zur Darstellung der Website.
|
||||
HTMLToPDF.pageWidth=Breite der Seite in Zentimetern. (Leer auf Standard)
|
||||
HTMLToPDF.pageHeight=Höhe der Seite in Zentimetern. (Leer auf Standard)
|
||||
HTMLToPDF.marginTop=Oberer Rand der Seite in Millimetern. (Leer auf Standard)
|
||||
HTMLToPDF.marginBottom=Unterer Rand der Seite in Millimetern. (Leer auf Standard)
|
||||
HTMLToPDF.marginLeft=Linker Rand der Seite in Millimetern. (Leer auf Standard)
|
||||
HTMLToPDF.marginRight=Linker Rand der Seite in Millimetern. (Leer auf Standard)
|
||||
HTMLToPDF.printBackground=Den Hintergrund der Website rendern.
|
||||
HTMLToPDF.zoom=Zoomstufe zur Darstellung der Website
|
||||
HTMLToPDF.pageWidth=Breite der Seite in Zentimetern (Leer auf Standard)
|
||||
HTMLToPDF.pageHeight=Höhe der Seite in Zentimetern (Leer auf Standard)
|
||||
HTMLToPDF.marginTop=Oberer Rand der Seite in Millimetern (Leer auf Standard)
|
||||
HTMLToPDF.marginBottom=Unterer Rand der Seite in Millimetern (Leer auf Standard)
|
||||
HTMLToPDF.marginLeft=Linker Rand der Seite in Millimetern (Leer auf Standard)
|
||||
HTMLToPDF.marginRight=Linker Rand der Seite in Millimetern (Leer auf Standard)
|
||||
HTMLToPDF.printBackground=Den Hintergrund der Website rendern
|
||||
HTMLToPDF.defaultHeader=Standardkopfzeile aktivieren (Name und Seitenzahl)
|
||||
HTMLToPDF.cssMediaType=CSS-Medientyp der Seite ändern.
|
||||
HTMLToPDF.cssMediaType=CSS-Medientyp der Seite ändern
|
||||
HTMLToPDF.none=Keine
|
||||
HTMLToPDF.print=Drucken
|
||||
HTMLToPDF.screen=Bildschirm
|
||||
|
@ -614,8 +621,8 @@ pageLayout.submit=Abschicken
|
|||
#scalePages
|
||||
scalePages.title=Seitengröße anpassen
|
||||
scalePages.header=Seitengröße anpassen
|
||||
scalePages.pageSize=Format der Seiten des Dokuments.
|
||||
scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite.
|
||||
scalePages.pageSize=Format der Seiten des Dokuments
|
||||
scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite
|
||||
scalePages.submit=Abschicken
|
||||
|
||||
|
||||
|
@ -759,7 +766,7 @@ compress.submit=Komprimieren
|
|||
#Add image
|
||||
addImage.title=Bild hinzufügen
|
||||
addImage.header=Ein Bild einfügen
|
||||
addImage.everyPage=Jede Seite?
|
||||
addImage.everyPage=In jede Seite einfügen?
|
||||
addImage.upload=Bild hinzufügen
|
||||
addImage.submit=Bild hinzufügen
|
||||
|
||||
|
@ -776,23 +783,23 @@ merge.submit=Zusammenführen
|
|||
pdfOrganiser.title=Seiten anordnen
|
||||
pdfOrganiser.header=PDF Seitenorganisation
|
||||
pdfOrganiser.submit=Seiten anordnen
|
||||
pdfOrganiser.mode=Mode
|
||||
pdfOrganiser.mode.1=Custom Page Order
|
||||
pdfOrganiser.mode.2=Reverse Order
|
||||
pdfOrganiser.mode.3=Duplex Sort
|
||||
pdfOrganiser.mode.4=Booklet Sort
|
||||
pdfOrganiser.mode.5=Side Stitch Booklet Sort
|
||||
pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
pdfOrganiser.mode=Modus
|
||||
pdfOrganiser.mode.1=Benutzerdefinierte Seitenreihenfolge
|
||||
pdfOrganiser.mode.2=Umgekehrte Reihenfolge
|
||||
pdfOrganiser.mode.3=Duplex-Sortierung
|
||||
pdfOrganiser.mode.4=Heftsortierung
|
||||
pdfOrganiser.mode.5=Seitenheftungs-Heftsortierung
|
||||
pdfOrganiser.mode.6=Ungerade-Gerade-Teilung
|
||||
pdfOrganiser.mode.7=Erste entfernen
|
||||
pdfOrganiser.mode.8=Letzte entfernen
|
||||
pdfOrganiser.mode.9=Erste und letzte entfernen
|
||||
pdfOrganiser.placeholder=(z.B. 1,3,2 oder 4-8,2,10-12 oder 2n-1)
|
||||
|
||||
|
||||
#multiTool
|
||||
multiTool.title=PDF-Multitool
|
||||
multiTool.header=PDF-Multitool
|
||||
multiTool.uploadPrompts=Please Upload PDF
|
||||
multiTool.uploadPrompts=Bitte PDF hochladen
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=PDF anzeigen
|
||||
|
@ -803,7 +810,7 @@ pageRemover.title=Seiten entfernen
|
|||
pageRemover.header=PDF Seiten entfernen
|
||||
pageRemover.pagesToDelete=Seiten zu entfernen (geben Sie eine Kommagetrennte Liste der Seitenzahlen an):
|
||||
pageRemover.submit=Seiten löschen
|
||||
pageRemover.placeholder=(e.g. 1,2,6 or 1-10,15-30)
|
||||
pageRemover.placeholder=(z.B. 1,2,6 oder 1-10,15-30)
|
||||
|
||||
|
||||
#rotate
|
||||
|
@ -892,7 +899,7 @@ watermark.selectText.8=Wasserzeichen Typ:
|
|||
watermark.selectText.9=Wasserzeichen-Bild:
|
||||
watermark.submit=Wasserzeichen hinzufügen
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
watermark.type.2=Bild
|
||||
|
||||
|
||||
#Change permissions
|
||||
|
@ -944,7 +951,8 @@ pdfToPDFA.title=PDF zu PDF/A
|
|||
pdfToPDFA.header=PDF zu PDF/A
|
||||
pdfToPDFA.credit=Dieser Dienst verwendet OCRmyPDF für die PDF/A-Konvertierung
|
||||
pdfToPDFA.submit=Konvertieren
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.tip=Dieser Dienst kann nur einzelne Eingangsdateien verarbeiten.
|
||||
pdfToPDFA.outputFormat=Ausgabeformat
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Anzahl vertikaler Teiler eingeben
|
|||
split-by-sections.submit=PDF teilen
|
||||
split-by-sections.merge=In eine PDF zusammenfügen
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Datei drucken
|
||||
printFile.header=Datei an Drucker senden
|
||||
printFile.selectText.1=Wähle die auszudruckende Datei
|
||||
printFile.selectText.2=Druckernamen eingeben
|
||||
printFile.submit=Drucken
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Lizenzen
|
||||
licenses.title=Lizenzen von Drittanbietern
|
||||
|
@ -1038,15 +1055,15 @@ licenses.license=Lizenz
|
|||
|
||||
|
||||
# error
|
||||
error.sorry=Sorry for the issue!
|
||||
error.needHelp=Need help / Found an issue?
|
||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
|
||||
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
|
||||
error.404.1=We can't seem to find the page you're looking for.
|
||||
error.404.2=Something went wrong
|
||||
error.github=Submit a ticket on GitHub
|
||||
error.showStack=Show Stack Trace
|
||||
error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
error.sorry=Entschuldigung für das Problem!
|
||||
error.needHelp=Brauchst du Hilfe / Ein Problem gefunden?
|
||||
error.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:
|
||||
error.404.head=404 - Seite nicht gefunden | Ups, wir sind im Code gestolpert!
|
||||
error.404.1=Wir können die gesuchte Seite nicht finden.
|
||||
error.404.2=Etwas ist schiefgelaufen
|
||||
error.github=Ein Ticket auf GitHub einreichen
|
||||
error.showStack=Stack-Trace anzeigen
|
||||
error.copyStack=Stack-Trace kopieren
|
||||
error.githubSubmit=GitHub - Ein Ticket einreichen
|
||||
error.discordSubmit=Discord - Unterstützungsbeitrag einreichen
|
||||
|
||||
|
|
|
@ -10,18 +10,18 @@ multiPdfDropPrompt=Επιλογή (ή τράβηγμα αρχείου και α
|
|||
imgPrompt=Επιλογή Εικόνας(Εικόνων)
|
||||
genericSubmit=Υποβολή
|
||||
processTimeWarning=Προσοχή: Αυτή η διαδικασία μπορεί να διαρκέσει έως και ένα λεπτό ανάλογα με το μέγεθος του αρχείου
|
||||
pageOrderPrompt=Προσαρμοσμένη Σειρά Σελίδας (Προσθέστε μία λίστε απο αριθμούς σελιδών, χωρισμένες με κόμμα ή συναρτήσεις όπως 2n+1) :
|
||||
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) :
|
||||
goToPage=Go
|
||||
pageOrderPrompt=Προσαρμοσμένη Σειρά Σελίδας (Προσθέστε μία λίστα απο αριθμούς σελιδών, χωρισμένες με κόμμα ή συναρτήσεις όπως 2n+1) :
|
||||
pageSelectionPrompt=Προσαρμοσμένη Επιλογή Σελίδας (Προσθέστε μία λίστα απο αριθμούς σελιδών, χωρισμένες με κόμμα 1,5,6 ή συναρτήσεις όπως 2n+1):
|
||||
goToPage=Πήγαινε
|
||||
true=Αληθές
|
||||
false=Λανθασμένο
|
||||
unknown=Άγνωστο
|
||||
save=Αποθήκευση
|
||||
saveToBrowser=Save to Browser
|
||||
saveToBrowser=Αποθήκευση στο Browser
|
||||
close=Κλείσιμο
|
||||
filesSelected=αρχεία που επιλέχθηκαν
|
||||
noFavourites=Κανένα αγαπήμενο δεν έχει προστεθεί
|
||||
downloadComplete=Download Complete
|
||||
downloadComplete=Η Λήψη Ολοκληρώθηκε
|
||||
bored=Βαριέστε να περιμένετε;
|
||||
alphabet=Αλφάβητο
|
||||
downloadPdf=Κατέβασμα του PDF
|
||||
|
@ -45,28 +45,30 @@ red=Κόκκινο
|
|||
green=Πράσινο
|
||||
blue=Μπλέ
|
||||
custom=Προσαρμογή...
|
||||
WorkInProgess=Work in progress, May not work or be buggy, Please report any problems!
|
||||
poweredBy=Powered by
|
||||
yes=Yes
|
||||
no=No
|
||||
WorkInProgess=Εργασία σε εξέλιξη, Ενδέχεται να μην λειτουργεί ή να έχει λάθη, Παρακαλώ αναφέρετε τυχόν προβλήματα!
|
||||
poweredBy=Τροφοδοτείται από
|
||||
yes=Ναι
|
||||
no=Όχι
|
||||
changedCredsMessage=Τα διαπιστευτήρια έχουν αλλάξει!
|
||||
notAuthenticatedMessage=Ο χρήστης δεν έχει αυθεντικοποιηθεί.
|
||||
userNotFoundMessage=Ο χρήστης δεν βρέθηκε.
|
||||
incorrectPasswordMessage=Ο τρέχων κωδικός πρόσβασης είναι λανθασμένος.
|
||||
usernameExistsMessage=Το νέο όνομα χρήστη υπάρχει ήδη.
|
||||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
goHomepage=Go to Homepage
|
||||
joinDiscord=Join our Discord server
|
||||
seeDockerHub=See Docker Hub
|
||||
visitGithub=Visit Github Repository
|
||||
donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
invalidUsernameMessage=Μη έγκυρο όνομα χρήστη, το όνομα χρήστη πρέπει να περιέχει μόνο αλφαβητικούς χαρακτήρες και αριθμούς.
|
||||
deleteCurrentUserMessage=Δεν είναι δυνατή η διαγραφή του τρέχοντος συνδεδεμένου χρήστη.
|
||||
deleteUsernameExistsMessage=Το όνομα χρήστη δεν υπάρχει και δεν μπορεί να διαγραφεί.
|
||||
downgradeCurrentUserMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη
|
||||
downgradeCurrentUserLongMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη. Ως εκ τούτου, ο τρέχων χρήστης δεν θα εμφανίζεται.
|
||||
error=Σφάλμα
|
||||
oops=Ωχ!
|
||||
help=Βοήθεια
|
||||
goHomepage=Πήγαινε στην Αρχική Σελίδα
|
||||
joinDiscord=Εγγραφείτε στο Server του Discord μας
|
||||
seeDockerHub=Βλέπε το Docker Hub
|
||||
visitGithub=Επισκεφθείτε το Αποθετήριο του Github
|
||||
donate=Δωρισε
|
||||
color=Χρώμα
|
||||
sponsor=Yποστηρικτής
|
||||
info=Info
|
||||
|
||||
|
||||
|
@ -74,26 +76,26 @@ info=Info
|
|||
###############
|
||||
# Pipeline #
|
||||
###############
|
||||
pipeline.header=Pipeline Menu (Beta)
|
||||
pipeline.header=Μενού Pipeline (Beta)
|
||||
pipeline.uploadButton=Upload Custom
|
||||
pipeline.configureButton=Configure
|
||||
pipeline.configureButton=Διαμόρφωσε
|
||||
pipeline.defaultOption=Custom
|
||||
pipeline.submitButton=Submit
|
||||
pipeline.help=Pipeline Help
|
||||
pipeline.scanHelp=Folder Scanning Help
|
||||
pipeline.submitButton=Υποβολή
|
||||
pipeline.help=Βοήθεια για το Pipeline
|
||||
pipeline.scanHelp=Βοήθεια για Σάρωση Φακέλων
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
######################
|
||||
pipelineOptions.header=Pipeline Configuration
|
||||
pipelineOptions.pipelineNameLabel=Pipeline Name
|
||||
pipelineOptions.saveSettings=Save Operation Settings
|
||||
pipelineOptions.pipelineNamePrompt=Enter pipeline name here
|
||||
pipelineOptions.selectOperation=Select Operation
|
||||
pipelineOptions.addOperationButton=Add operation
|
||||
pipelineOptions.header=Διαμόρφωση Pipeline
|
||||
pipelineOptions.pipelineNameLabel=Όνομα Pipeline
|
||||
pipelineOptions.saveSettings=Αποθήκευση Ρυθμίσεων Λειτουργίας
|
||||
pipelineOptions.pipelineNamePrompt=Εισαγάγετε το όνομα του pipeline εδώ
|
||||
pipelineOptions.selectOperation=Επιλέξτε Λειτουργία
|
||||
pipelineOptions.addOperationButton=Προσθήκη λειτουργίας
|
||||
pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.saveButton=Download
|
||||
pipelineOptions.validateButton=Validate
|
||||
pipelineOptions.saveButton=Λήψη
|
||||
pipelineOptions.validateButton=Επικυρώνω
|
||||
|
||||
|
||||
|
||||
|
@ -117,20 +119,22 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Ρυθμίσεις
|
||||
settings.update=Υπάρχει διαθέσιμη ενημέρωση
|
||||
settings.appVersion=Έκδοση εφαρμογής: App Version:
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Έκδοση εφαρμογής:
|
||||
settings.downloadOption.title=Επιλέξετε την επιλογή λήψης (Για λήψεις μεμονωμένων αρχείων χωρίς zip):
|
||||
settings.downloadOption.1=Άνοιγμα στο ίδιο παράθυρο
|
||||
settings.downloadOption.2=Άνοιγμα σε νέο παράθυρο
|
||||
settings.downloadOption.3=Λήψη αρχείου
|
||||
settings.zipThreshold=Zip αρχεία όταν ο αριθμός των ληφθέντων αρχείων είναι πολύ μεγάλος
|
||||
settings.zipThreshold=Αρχεία Zip όταν ο αριθμός των ληφθέντων αρχείων είναι πολύ μεγάλος
|
||||
settings.signOut=Αποσύνδεση
|
||||
settings.accountSettings=Ρυθμίσεις Λογαριασμού
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Αλλαγή Διαπιστευτηρίων
|
||||
changeCreds.header=Ενημέρωση των λεπτομερειών του Λογαριασμού σας
|
||||
changeCreds.changePassword=You are using default login credentials. Please enter a new password
|
||||
changeCreds.changePassword=Χρησιμοποιείτε προεπιλεγμένα διαπιστευτήρια σύνδεσης. Εισαγάγετε έναν νέο κωδικό πρόσβασης
|
||||
changeCreds.newUsername=Νέο Όνομα Χρήστη
|
||||
changeCreds.oldPassword=Τρέχων Κωδικός Πρόσβασης
|
||||
changeCreds.newPassword=Νέος Κωδικός Πρόσβασης
|
||||
|
@ -170,18 +174,19 @@ adminUserSettings.roles=Ρόλοι
|
|||
adminUserSettings.role=Ρόλος
|
||||
adminUserSettings.actions=Ενέργειες
|
||||
adminUserSettings.apiUser=Περιορισμένος Χρήστης για διεπαφή προγραμματισμού εφαρμογών (API User)
|
||||
adminUserSettings.extraApiUser=Additional Limited API User
|
||||
adminUserSettings.extraApiUser=Πρόσθετος Περιορισμένος Χρήστης για Διεπαφή Προγραμματισμού Εφαρμογών (API User)
|
||||
adminUserSettings.webOnlyUser=Χρήστης μόνο Ιστού
|
||||
adminUserSettings.demoUser=Demo User (No custom settings)
|
||||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.demoUser=Demo χρήστης (Χωρίς προσαρμοσμένες ρυθμίσεις)
|
||||
adminUserSettings.internalApiUser=Εσωτερικός API χρήστης
|
||||
adminUserSettings.forceChange=Αναγκάστε τον χρήστη να αλλάξει το όνομα χρήστη/κωδικό πρόσβασης κατά τη σύνδεση
|
||||
adminUserSettings.submit=Αποθήκευση Χρήστη
|
||||
adminUserSettings.changeUserRole=Αλλαγή ρόλου χρήστη
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
#############
|
||||
home.desc=Η τοπικά φιλοξενούμενη one-stop-shop σας για όλες τις ανάγκες σας σε PDF.
|
||||
home.searchBar=Search for features...
|
||||
home.searchBar=Αναζήτηση για χαρακτηριστικά...
|
||||
|
||||
|
||||
home.viewPdf.title=Εμφάνιση PDF
|
||||
|
@ -202,7 +207,7 @@ split.tags=Page operations,divide,Multi Page,cut,server side
|
|||
|
||||
home.rotate.title=Περιστροφή
|
||||
home.rotate.desc=Περιστροφή των PDF σας με εύκολο τρόπο.
|
||||
rotate.tags=server side
|
||||
rotate.tags=από την πλευρά του server
|
||||
|
||||
|
||||
home.imageToPdf.title=Εικόνα σε PDF
|
||||
|
@ -395,35 +400,35 @@ home.autoRedact.title=Αυτόματο Μαύρισμα Κειμένου
|
|||
home.autoRedact.desc=Αυτόματη επεξεργασία (Μαύρισμα) κείμενου σε PDF με βάση το κείμενο εισαγωγής
|
||||
autoRedact.tags=Redact,Hide,black out,black,marker,hidden
|
||||
|
||||
home.tableExtraxt.title=PDF to CSV
|
||||
home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV
|
||||
home.tableExtraxt.title=PDF σε CSV
|
||||
home.tableExtraxt.desc=Εξάγει πίνακες από PDF μετατρέποντάς το σε CSV
|
||||
tableExtraxt.tags=CSV,Table Extraction,extract,convert
|
||||
|
||||
|
||||
home.autoSizeSplitPDF.title=Auto Split by Size/Count
|
||||
home.autoSizeSplitPDF.desc=Split a single PDF into multiple documents based on size, page count, or document count
|
||||
home.autoSizeSplitPDF.title=Αυτόματη διαίρεση κατά μέγεθος/πλήθος
|
||||
home.autoSizeSplitPDF.desc=Διαχωρίστε ένα μόνο PDF σε πολλά έγγραφα με βάση το μέγεθος, τον αριθμό σελίδων ή τον αριθμό εγγράφων
|
||||
autoSizeSplitPDF.tags=pdf,split,document,organization
|
||||
|
||||
|
||||
home.overlay-pdfs.title=Overlay PDFs
|
||||
home.overlay-pdfs.desc=Overlays PDFs on-top of another PDF
|
||||
home.overlay-pdfs.title=Επικάλυψη PDFs
|
||||
home.overlay-pdfs.desc=Επικαλύπτει αρχεία PDF πάνω σε άλλο PDF
|
||||
overlay-pdfs.tags=Overlay
|
||||
|
||||
home.split-by-sections.title=Split PDF by Sections
|
||||
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections
|
||||
home.split-by-sections.title=Διαχωρισμός PDF ανά ενότητες
|
||||
home.split-by-sections.desc=Διαχωρίστε κάθε σελίδα ενός PDF σε μικρότερες οριζόντιες και κάθετες ενότητες
|
||||
split-by-sections.tags=Section Split, Divide, Customize
|
||||
|
||||
home.AddStampRequest.title=Add Stamp to PDF
|
||||
home.AddStampRequest.desc=Add text or add image stamps at set locations
|
||||
home.AddStampRequest.title=Προσθήκη σφραγίδας σε PDF
|
||||
home.AddStampRequest.desc=Προσθέστε κείμενο ή προσθέστε σφραγίδες εικόνας σε καθορισμένες τοποθεσίες
|
||||
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
|
||||
|
||||
|
||||
home.PDFToBook.title=PDF to Book
|
||||
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre
|
||||
home.PDFToBook.title=PDF σε Book
|
||||
home.PDFToBook.desc=Μετατρέπει τις μορφές PDF σε Book/Comic χρησιμοποιώντας calibre
|
||||
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
home.BookToPDF.title=Book to PDF
|
||||
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre
|
||||
home.BookToPDF.title=Book σε PDF
|
||||
home.BookToPDF.desc=Μετατρέπει τις μορφές Books/Comics σε PDF χρησιμοποιώντας calibre
|
||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||
|
||||
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Να Με Θυμάσαι
|
|||
login.invalid=Λάθος όνομα χρήστη ή κωδικού πρόσβασης.
|
||||
login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
|
||||
login.signinTitle=Παρακαλώ, συνδεθείτε
|
||||
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
|
||||
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -472,7 +479,7 @@ pdfToSinglePage.submit=Μετατροπή σε Μονή Σελίδα
|
|||
pageExtracter.title=Εξαγωγή Σελίδων
|
||||
pageExtracter.header=EΕξαγωγή Σελίδων
|
||||
pageExtracter.submit=Εξαγωγή
|
||||
pageExtracter.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
|
||||
pageExtracter.placeholder=(π.χ. 1,2,8 ή 4,7,12-16 ή 2n-1)
|
||||
|
||||
|
||||
#getPdfInfo
|
||||
|
@ -504,37 +511,37 @@ HTMLToPDF.header=HTML σε PDF
|
|||
HTMLToPDF.help=Δέχεται αρχεία τύπου HTML και τύπου ZIP που περιέχουν html/css/εικόνες κ.λπ. που απαιτούνται
|
||||
HTMLToPDF.submit=Μετατροπή
|
||||
HTMLToPDF.credit=Χρησιμοποιεί WeasyPrint
|
||||
HTMLToPDF.zoom=Zoom level for displaying the website.
|
||||
HTMLToPDF.pageWidth=Width of the page in centimeters. (Blank to default)
|
||||
HTMLToPDF.pageHeight=Height of the page in centimeters. (Blank to default)
|
||||
HTMLToPDF.marginTop=Top margin of the page in millimeters. (Blank to default)
|
||||
HTMLToPDF.marginBottom=Bottom margin of the page in millimeters. (Blank to default)
|
||||
HTMLToPDF.marginLeft=Left margin of the page in millimeters. (Blank to default)
|
||||
HTMLToPDF.marginRight=Right margin of the page in millimeters. (Blank to default)
|
||||
HTMLToPDF.printBackground=Render the background of websites.
|
||||
HTMLToPDF.defaultHeader=Enable Default Header (Name and page number)
|
||||
HTMLToPDF.cssMediaType=Change the CSS media type of the page.
|
||||
HTMLToPDF.none=None
|
||||
HTMLToPDF.print=Print
|
||||
HTMLToPDF.zoom=Επίπεδο ζουμ για την εμφάνιση του ιστότοπου.
|
||||
HTMLToPDF.pageWidth=Το πλάτος της σελίδας σε εκατοστά. (Κενό έως προεπιλογή)
|
||||
HTMLToPDF.pageHeight=Το ύψος της σελίδας σε εκατοστά. (Κενό έως προεπιλογή)
|
||||
HTMLToPDF.marginTop=Το επάνω περιθώριο της σελίδας σε χιλιοστά. (Κενό έως προεπιλογή)
|
||||
HTMLToPDF.marginBottom=Κάτω περιθώριο σελίδας σε χιλιοστά. (Κενό έως προεπιλογή)
|
||||
HTMLToPDF.marginLeft=Το αριστερό περιθώριο της σελίδας σε χιλιοστά. (Κενό έως προεπιλογή)
|
||||
HTMLToPDF.marginRight=Το δεξί περιθώριο της σελίδας σε χιλιοστά. (Κενό έως προεπιλογή)
|
||||
HTMLToPDF.printBackground=Αποδώστε το φόντο των ιστοσελίδων.
|
||||
HTMLToPDF.defaultHeader=Ενεργοποίηση προεπιλεγμένης κεφαλίδας (Όνομα και αριθμός σελίδας)
|
||||
HTMLToPDF.cssMediaType=Αλλάξτε τον τύπο μέσων (media type )CSS της σελίδας.
|
||||
HTMLToPDF.none=Κανένα
|
||||
HTMLToPDF.print=Εκτύπωσε
|
||||
HTMLToPDF.screen=Screen
|
||||
|
||||
|
||||
#AddStampRequest
|
||||
AddStampRequest.header=Stamp PDF
|
||||
AddStampRequest.title=Stamp PDF
|
||||
AddStampRequest.stampType=Stamp Type
|
||||
AddStampRequest.stampText=Stamp Text
|
||||
AddStampRequest.stampImage=Stamp Image
|
||||
AddStampRequest.alphabet=Alphabet
|
||||
AddStampRequest.fontSize=Font/Image Size
|
||||
AddStampRequest.rotation=Rotation
|
||||
AddStampRequest.opacity=Opacity
|
||||
AddStampRequest.position=Position
|
||||
AddStampRequest.overrideX=Override X Coordinate
|
||||
AddStampRequest.overrideY=Override Y Coordinate
|
||||
AddStampRequest.customMargin=Custom Margin
|
||||
AddStampRequest.customColor=Custom Text Color
|
||||
AddStampRequest.submit=Submit
|
||||
AddStampRequest.header=Σφραγίδα PDF
|
||||
AddStampRequest.title=Σφραγίδα PDF
|
||||
AddStampRequest.stampType=Τύπος σφραγίδας
|
||||
AddStampRequest.stampText=Κείμενο σφραγίδας
|
||||
AddStampRequest.stampImage=Εικόνα σφραγίδας
|
||||
AddStampRequest.alphabet=Αλφάβητο
|
||||
AddStampRequest.fontSize=Μέγεθος γραμματοσειράς/εικόνας
|
||||
AddStampRequest.rotation=Περιστροφή
|
||||
AddStampRequest.opacity=Αδιαφάνεια
|
||||
AddStampRequest.position=Θέση
|
||||
AddStampRequest.overrideX=Override Συντεταγμένης X
|
||||
AddStampRequest.overrideY=Override Συντεταγμένης Ψ
|
||||
AddStampRequest.customMargin=Προσαρμοσμένο Περιθώριο
|
||||
AddStampRequest.customColor=Προσαρμοσμένο χρώμα κειμένου
|
||||
AddStampRequest.submit=Υποβολή
|
||||
|
||||
|
||||
#sanitizePDF
|
||||
|
@ -623,11 +630,11 @@ scalePages.submit=Υποβολή
|
|||
certSign.title=Υπογραφή Πιστοποιητικού
|
||||
certSign.header=Υπογράψτε ένα αρχείο PDF με το πιστοποιητικό σας (Εργασία σε εξέλιξη)
|
||||
certSign.selectPDF=Επιλογή αρχείου PDF για υπογραφή:
|
||||
certSign.jksNote=Note: If your certificate type is not listed below, please convert it to a Java Keystore (.jks) file using the keytool command line tool. Then, choose the .jks file option below.
|
||||
certSign.jksNote=Σημείωση: Εάν ο τύπος πιστοποιητικού σας δεν αναφέρεται παρακάτω, μετατρέψτε το σε αρχείο Java Keystore (.jks) χρησιμοποιώντας το εργαλείο γραμμής εντολών keytool. Στη συνέχεια, επιλέξτε την επιλογή αρχείου .jks παρακάτω.
|
||||
certSign.selectKey=Επιλέξτε το αρχείο του ιδιωτικού κλειδιού σας (μορφή PKCS#8, μπορεί να είναι .pem ή .der):
|
||||
certSign.selectCert=Επιλέξτε το αρχείο πιστοποιητικού σας (μορφή X.509, μπορεί να είναι .pem ή .der):
|
||||
certSign.selectP12=Επιλέξτε το αρχείο PKCS#12 Keystore (.p12 ή .pfx) (Προαιρετικό, εάν παρέχεται, θα πρέπει να περιέχει το ιδιωτικό κλειδί και το πιστοποιητικό σας):
|
||||
certSign.selectJKS=Select Your Java Keystore File (.jks or .keystore):
|
||||
certSign.selectJKS=Επιλέξτε το αρχείο Java Keystore (.jks ή .keystore):
|
||||
certSign.certType=Τύπος Πιστοποιητικού
|
||||
certSign.password=Εισαγάγετε τον κωδικό του Keystore ή του Ιδιωτικού Κλειδιού (εάν υπάρχει):
|
||||
certSign.showSig=Εμφάνιση Υπογραφής
|
||||
|
@ -648,9 +655,9 @@ removeBlanks.submit=Αφαίρεση Κενών
|
|||
|
||||
|
||||
#removeAnnotations
|
||||
removeAnnotations.title=Remove Annotations
|
||||
removeAnnotations.header=Remove Annotations
|
||||
removeAnnotations.submit=Remove
|
||||
removeAnnotations.title=Κατάργηση σχολιασμών
|
||||
removeAnnotations.header=Κατάργηση σχολιασμών
|
||||
removeAnnotations.submit=Κατάργηση
|
||||
|
||||
|
||||
#compare
|
||||
|
@ -661,17 +668,17 @@ compare.document.2=Έγγραφο 2
|
|||
compare.submit=Σύγκριση
|
||||
|
||||
#BookToPDF
|
||||
BookToPDF.title=Books and Comics to PDF
|
||||
BookToPDF.header=Book to PDF
|
||||
BookToPDF.credit=Uses Calibre
|
||||
BookToPDF.submit=Convert
|
||||
BookToPDF.title=Books και Comics σε PDF
|
||||
BookToPDF.header=Book σε PDF
|
||||
BookToPDF.credit=Χρησιμοποιεί Caliber
|
||||
BookToPDF.submit=Μετατροπή
|
||||
|
||||
#PDFToBook
|
||||
PDFToBook.title=PDF to Book
|
||||
PDFToBook.header=PDF to Book
|
||||
PDFToBook.selectText.1=Format
|
||||
PDFToBook.credit=Uses Calibre
|
||||
PDFToBook.submit=Convert
|
||||
PDFToBook.title=PDF σε Book
|
||||
PDFToBook.header=PDF σε Book
|
||||
PDFToBook.selectText.1=Μορφή
|
||||
PDFToBook.credit=Χρησιμοποιεί Caliber
|
||||
PDFToBook.submit=Μετατροπή
|
||||
|
||||
#sign
|
||||
sign.title=Υπογραφή
|
||||
|
@ -759,7 +766,7 @@ compress.submit=Συμπίεση
|
|||
#Add image
|
||||
addImage.title=Προσθήκη Εικόνας
|
||||
addImage.header=Προσθήκη Εικόνας σε PDF
|
||||
addImage.everyPage=Κάθε Σελίδα?
|
||||
addImage.everyPage=Κάθε Σελίδα;
|
||||
addImage.upload=Προσθήκη Εικόνας
|
||||
addImage.submit=Προσθήκη Εικόνας
|
||||
|
||||
|
@ -777,40 +784,40 @@ pdfOrganiser.title=Διοργανωτής σελίδας
|
|||
pdfOrganiser.header=Διοργανωτής σελίδας PDF
|
||||
pdfOrganiser.submit=Αναδιάταξη σελίδων
|
||||
pdfOrganiser.mode=Mode
|
||||
pdfOrganiser.mode.1=Custom Page Order
|
||||
pdfOrganiser.mode.2=Reverse Order
|
||||
pdfOrganiser.mode.3=Duplex Sort
|
||||
pdfOrganiser.mode.4=Booklet Sort
|
||||
pdfOrganiser.mode.1=Προσαρμοσμένη Σειρά Σελίδας
|
||||
pdfOrganiser.mode.2=Αντίστροφη Σειρά
|
||||
pdfOrganiser.mode.3=Ταξινόμηση Διπλής Όψης (Duplex Sort)
|
||||
pdfOrganiser.mode.4=Ταξινόμηση Φυλλαδίου (Booklet Sort)
|
||||
pdfOrganiser.mode.5=Side Stitch Booklet Sort
|
||||
pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
pdfOrganiser.mode.6=Διαίρεση Μονού-Ζυγού
|
||||
pdfOrganiser.mode.7=Αφαίρεση Πρώτου
|
||||
pdfOrganiser.mode.8=Αφαίρεση Τελευταίου
|
||||
pdfOrganiser.mode.9=Αφαίρεση Πρώτου και Τελευταίου
|
||||
pdfOrganiser.placeholder=(π.χ. 1,3,2 ή 4-8,2,10-12 ή 2n-1)
|
||||
|
||||
|
||||
#multiTool
|
||||
multiTool.title=PDF Πολυεργαλείο
|
||||
multiTool.header=PDF Πολυεργαλείο
|
||||
multiTool.uploadPrompts=Please Upload PDF
|
||||
multiTool.uploadPrompts=Ανεβάστε το PDF
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=View PDF
|
||||
viewPdf.header=View PDF
|
||||
viewPdf.title=Προβολή PDF
|
||||
viewPdf.header=Προβολή PDF
|
||||
|
||||
#pageRemover
|
||||
pageRemover.title=Αφαιρετής Σελίδων
|
||||
pageRemover.header=Αφαιρετής Σελίδων PDF
|
||||
pageRemover.pagesToDelete=Σελίδες προς διαγραφή (Εισαγάγετε μια λίστα με αριθμούς σελίδων διαχωρισμένων με κόμματα):
|
||||
pageRemover.submit=Αφαίρεση Σελίδων
|
||||
pageRemover.placeholder=(e.g. 1,2,6 or 1-10,15-30)
|
||||
pageRemover.placeholder=(π.χ. 1,2,6 ή 1-10,15-30)
|
||||
|
||||
|
||||
#rotate
|
||||
rotate.title=Περιστροφή PDF
|
||||
rotate.header=Περιστροφή PDF
|
||||
rotate.selectAngle=Select rotation angle (in multiples of 90 degrees):
|
||||
rotate.submit=Rotate
|
||||
rotate.selectAngle=Επιλέξτε γωνία περιστροφής (σε πολλαπλάσια των 90 μοιρών):
|
||||
rotate.submit=Περιστροφή
|
||||
|
||||
|
||||
#split-pdfs
|
||||
|
@ -891,8 +898,8 @@ watermark.selectText.7=Αδιαφάνεια (Opacity) (0% - 100%):
|
|||
watermark.selectText.8=Τύπος Υδατογραφήματος:
|
||||
watermark.selectText.9=Εικόνα Υδατογραφήματος:
|
||||
watermark.submit=Προσθήκη Υδατογραφήματος
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
watermark.type.1=Κείμενο
|
||||
watermark.type.2=Εικόνα
|
||||
|
||||
|
||||
#Change permissions
|
||||
|
@ -944,7 +951,8 @@ pdfToPDFA.title=PDF σε PDF/A
|
|||
pdfToPDFA.header=PDF σε PDF/A
|
||||
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF για PDF/A μετατροπή
|
||||
pdfToPDFA.submit=Μετατροπή
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -985,68 +993,77 @@ PDFToXML.credit=Αυτή η υπηρεσία χρησιμοποιεί LibreOffic
|
|||
PDFToXML.submit=Μετατροπή
|
||||
|
||||
#PDFToCSV
|
||||
PDFToCSV.title=PDF ?? CSV
|
||||
PDFToCSV.header=PDF ?? CSV
|
||||
PDFToCSV.prompt=Choose page to extract table
|
||||
PDFToCSV.submit=?????????
|
||||
PDFToCSV.title=PDF σε CSV
|
||||
PDFToCSV.header=PDF σε CSV
|
||||
PDFToCSV.prompt=Επιλέξτε σελίδα για εξαγωγή πίνακα
|
||||
PDFToCSV.submit=Εξαξωγή
|
||||
|
||||
#split-by-size-or-count
|
||||
split-by-size-or-count.title=Split PDF by Size or Count
|
||||
split-by-size-or-count.header=Split PDF by Size or Count
|
||||
split-by-size-or-count.type.label=Select Split Type
|
||||
split-by-size-or-count.type.size=By Size
|
||||
split-by-size-or-count.type.pageCount=By Page Count
|
||||
split-by-size-or-count.type.docCount=By Document Count
|
||||
split-by-size-or-count.value.label=Enter Value
|
||||
split-by-size-or-count.value.placeholder=Enter size (e.g., 2MB or 3KB) or count (e.g., 5)
|
||||
split-by-size-or-count.submit=Submit
|
||||
split-by-size-or-count.title=Διαχωρίστε το PDF Κατά Μέγεθος ή Μέτρηση
|
||||
split-by-size-or-count.header=Διαχωρίστε το PDF Κατά Μέγεθος ή Μέτρηση
|
||||
split-by-size-or-count.type.label=Επιλέξτε Τύπος Διαχωρισμού
|
||||
split-by-size-or-count.type.size=Ανά Μέγεθος
|
||||
split-by-size-or-count.type.pageCount=Ανά αριθμό σελίδων
|
||||
split-by-size-or-count.type.docCount=Ανά αριθμό εγγράφων
|
||||
split-by-size-or-count.value.label=Εισαγάγετε τιμή
|
||||
split-by-size-or-count.value.placeholder=Εισαγάγετε μέγεθος (π.χ. 2MB ή 3KB) ή μέτρηση (π.χ. 5)
|
||||
split-by-size-or-count.submit=Υποβολή
|
||||
|
||||
|
||||
#overlay-pdfs
|
||||
overlay-pdfs.header=Overlay PDF Files
|
||||
overlay-pdfs.baseFile.label=Select Base PDF File
|
||||
overlay-pdfs.overlayFiles.label=Select Overlay PDF Files
|
||||
overlay-pdfs.mode.label=Select Overlay Mode
|
||||
overlay-pdfs.mode.sequential=Sequential Overlay
|
||||
overlay-pdfs.mode.interleaved=Interleaved Overlay
|
||||
overlay-pdfs.mode.fixedRepeat=Fixed Repeat Overlay
|
||||
overlay-pdfs.counts.label=Overlay Counts (for Fixed Repeat Mode)
|
||||
overlay-pdfs.counts.placeholder=Enter comma-separated counts (e.g., 2,3,1)
|
||||
overlay-pdfs.position.label=Select Overlay Position
|
||||
overlay-pdfs.header=Επικάλυψη αρχείων PDF
|
||||
overlay-pdfs.baseFile.label=Επιλέξτε Βασικό αρχείο PDF
|
||||
overlay-pdfs.overlayFiles.label=Επιλέξτε Επικάλυψη αρχείων PDF
|
||||
overlay-pdfs.mode.label=Επιλέξτε Λειτουργία Επικάλυψης
|
||||
overlay-pdfs.mode.sequential=Διαδοχική Επικάλυψη
|
||||
overlay-pdfs.mode.interleaved=Επικάλυψη με Παρεμβολή
|
||||
overlay-pdfs.mode.fixedRepeat=Διορθώθηκε η Επικάλυψη Επανάληψης
|
||||
overlay-pdfs.counts.label=Μετρήσεις επικάλυψης (για σταθερή λειτουργία επανάληψης)
|
||||
overlay-pdfs.counts.placeholder=Εισαγάγετε μετρήσεις διαχωρισμένες με κόμματα (π.χ. 2,3,1)
|
||||
overlay-pdfs.position.label=Επιλέξτε Θέση Επικάλυψης
|
||||
overlay-pdfs.position.foreground=Foreground
|
||||
overlay-pdfs.position.background=Background
|
||||
overlay-pdfs.submit=Submit
|
||||
overlay-pdfs.submit=Υποβολή
|
||||
|
||||
|
||||
#split-by-sections
|
||||
split-by-sections.title=Split PDF by Sections
|
||||
split-by-sections.header=Split PDF into Sections
|
||||
split-by-sections.horizontal.label=Horizontal Divisions
|
||||
split-by-sections.vertical.label=Vertical Divisions
|
||||
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions
|
||||
split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
||||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
split-by-sections.title=Διαχωρισμός PDF ανά ενότητες
|
||||
split-by-sections.header=Διαχωρίστε το PDF σε Ενότητες
|
||||
split-by-sections.horizontal.label=Οριζόντιες Διαιρέσεις
|
||||
split-by-sections.vertical.label=Κάθετες Διαιρέσεις
|
||||
split-by-sections.horizontal.placeholder=Εισαγάγετε τον αριθμό των οριζόντιων διαιρέσεων
|
||||
split-by-sections.vertical.placeholder=Εισαγάγετε τον αριθμό των κάθετων διαιρέσεων
|
||||
split-by-sections.submit=Διαχωρισμός PDF
|
||||
split-by-sections.merge=Συγχώνευση σε ένα PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
licenses.header=3rd Party Licenses
|
||||
licenses.nav=Άδειες
|
||||
licenses.title=3rd Party Άδειες
|
||||
licenses.header=3rd Party Άδειες
|
||||
licenses.module=Module
|
||||
licenses.version=Version
|
||||
licenses.license=License
|
||||
licenses.version=Εκδοχή
|
||||
licenses.license=Άδεια
|
||||
|
||||
|
||||
# error
|
||||
error.sorry=Sorry for the issue!
|
||||
error.needHelp=Need help / Found an issue?
|
||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
|
||||
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
|
||||
error.404.1=We can't seem to find the page you're looking for.
|
||||
error.404.2=Something went wrong
|
||||
error.github=Submit a ticket on GitHub
|
||||
error.showStack=Show Stack Trace
|
||||
error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
error.sorry=Συγγνώμη για το ζήτημα!
|
||||
error.needHelp=Χρειάζεστε βοήθεια / Βρήκατε πρόβλημα;
|
||||
error.contactTip=Εάν εξακολουθείτε να αντιμετωπίζετε προβλήματα, μη διστάσετε να επικοινωνήσετε μαζί μας για βοήθεια. Μπορείτε να υποβάλετε ένα ticket στη σελίδα μας στο GitHub ή να επικοινωνήσετε μαζί μας μέσω του Discord:
|
||||
error.404.head=404 - Η σελίδα δεν βρέθηκε | Oops, we tripped in the code!
|
||||
error.404.1=Δεν μπορούμε να βρούμε τη σελίδα που ψάχνετε.
|
||||
error.404.2=Κάτι πήγε στραβά
|
||||
error.github=Υποβάλετε ένα ticket στο GitHub
|
||||
error.showStack=Εμφάνιση Stack Trace
|
||||
error.copyStack=Αντιγραφή Stack Trace
|
||||
error.githubSubmit=GitHub - Υποβάλετε ένα ticket
|
||||
error.discordSubmit=Discord - Υποβάλετε ένα Support post
|
||||
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Cannot downgrade current user's role
|
||||
downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Settings
|
||||
settings.update=Update available
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=App Version:
|
||||
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
||||
settings.downloadOption.1=Open in same window
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Download file
|
|||
settings.zipThreshold=Zip files when the number of downloaded files exceeds
|
||||
settings.signOut=Sign Out
|
||||
settings.accountSettings=Account Settings
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Change User's Role
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Remember me
|
|||
login.invalid=Invalid username or password.
|
||||
login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Login via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF To PDF/A
|
|||
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
||||
pdfToPDFA.submit=Convert
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Cannot downgrade current user's role
|
||||
downgradeCurrentUserLongMessage=Cannot downgrade current user's role. Hence, current user will not be shown.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -116,6 +118,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Settings
|
||||
settings.update=Update available
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=App Version:
|
||||
settings.downloadOption.title=Choose download option (For single file non zip downloads):
|
||||
settings.downloadOption.1=Open in same window
|
||||
|
@ -124,8 +127,9 @@ settings.downloadOption.3=Download file
|
|||
settings.zipThreshold=Zip files when the number of downloaded files exceeds
|
||||
settings.signOut=Sign Out
|
||||
settings.accountSettings=Account Settings
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -175,6 +179,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Change User's Role
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -439,6 +444,8 @@ login.rememberme=Remember me
|
|||
login.invalid=Invalid username or password.
|
||||
login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Login via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -944,6 +951,7 @@ pdfToPDFA.header=PDF To PDF/A
|
|||
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
|
||||
pdfToPDFA.submit=Convert
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1027,6 +1035,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,20 +57,25 @@ usernameExistsMessage=El nuevo nombre de usuario está en uso.
|
|||
invalidUsernameMessage=Nombre de usuario no válido, El nombre de ususario debe contener únicamente números y caracteres alfabéticos.
|
||||
deleteCurrentUserMessage=No puede eliminar el usuario que tiene la sesión actualmente en uso.
|
||||
deleteUsernameExistsMessage=El usuario no existe y no puede eliminarse.
|
||||
downgradeCurrentUserMessage=No se puede degradar el rol del usuario actual
|
||||
downgradeCurrentUserLongMessage=No se puede degradar el rol del usuario actual. Por lo tanto, el usuario actual no se mostrará.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
oops=Ups!
|
||||
help=Help
|
||||
goHomepage=Go to Homepage
|
||||
joinDiscord=Join our Discord server
|
||||
seeDockerHub=See Docker Hub
|
||||
visitGithub=Visit Github Repository
|
||||
donate=Donate
|
||||
goHomepage=Ir a la página principal
|
||||
joinDiscord=Únase a nuestro servidor Discord
|
||||
seeDockerHub=Ver Docker Hub
|
||||
visitGithub=Visitar Repositorio de Github
|
||||
donate=Donar
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
sponsor=Patrocinador
|
||||
info=Info
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
###############
|
||||
# Pipeline #
|
||||
###############
|
||||
|
@ -79,8 +84,8 @@ pipeline.uploadButton=Cargar personalización
|
|||
pipeline.configureButton=Configurar
|
||||
pipeline.defaultOption=Personalizar
|
||||
pipeline.submitButton=Enviar
|
||||
pipeline.help=Pipeline Help
|
||||
pipeline.scanHelp=Folder Scanning Help
|
||||
pipeline.help=Ayuda de Canalización
|
||||
pipeline.scanHelp=Ayuda de escaneado de carpetas
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
|
@ -117,6 +122,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Configuración
|
||||
settings.update=Actualización disponible
|
||||
settings.updateAvailable={0} es la versión instalada. Hay disponible una versión nueva ({1}).
|
||||
settings.appVersion=Versión de la aplicación:
|
||||
settings.downloadOption.title=Elegir la opción de descarga (para descargas de un solo archivo sin ZIP):
|
||||
settings.downloadOption.1=Abrir en la misma ventana
|
||||
|
@ -125,8 +131,9 @@ settings.downloadOption.3=Descargar el archivo
|
|||
settings.zipThreshold=Archivos ZIP cuando excede el número de archivos descargados
|
||||
settings.signOut=Desconectar
|
||||
settings.accountSettings=Configuración de la cuenta
|
||||
|
||||
|
||||
settings.bored.help=Habilita el juego del huevo de pascua
|
||||
settings.cacheInputs.name=Guardar entradas del formulario
|
||||
settings.cacheInputs.help=Habilitar guardar entradas previamente utilizadas para futuras acciones
|
||||
|
||||
changeCreds.title=Cambiar Credenciales
|
||||
changeCreds.header=Actualice los detalles de su cuenta
|
||||
|
@ -176,6 +183,7 @@ adminUserSettings.demoUser=Usuario Demo (Sin ajustes personalizados)
|
|||
adminUserSettings.internalApiUser=Usuario interno de API
|
||||
adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso
|
||||
adminUserSettings.submit=Guardar Usuario
|
||||
adminUserSettings.changeUserRole=Cambiar rol de usuario
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -250,7 +258,7 @@ compressPdfs.tags=aplastar,pequeño,diminuto
|
|||
|
||||
home.changeMetadata.title=Cambiar metadatos
|
||||
home.changeMetadata.desc=Cambiar/Eliminar/Añadir metadatos al documento PDF
|
||||
changeMetadata.tags==Título,autor,fecha,creación,hora,editorial,productor,estadísticas
|
||||
changeMetadata.tags=título,autor,fecha,creación,hora,editorial,productor,estadísticas
|
||||
|
||||
home.fileToPDF.title=Convertir archivo a PDF
|
||||
home.fileToPDF.desc=Convertir casi cualquier archivo a PDF (DOCX, PNG, XLS, PPT, TXT y más)
|
||||
|
@ -440,6 +448,8 @@ login.rememberme=Recordarme
|
|||
login.invalid=Nombre de usuario o contraseña erróneos.
|
||||
login.locked=Su cuenta se ha bloqueado.
|
||||
login.signinTitle=Por favor, inicie sesión
|
||||
login.ssoSignIn=Iniciar sesión a través del inicio de sesión único
|
||||
login.oauth2AutoCreateDisabled=Usuario DE creación automática de OAUTH2 DESACTIVADO
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -776,23 +786,23 @@ merge.submit=Unir
|
|||
pdfOrganiser.title=Organizador de páginas
|
||||
pdfOrganiser.header=Organizador de páginas PDF
|
||||
pdfOrganiser.submit=Organizar páginas
|
||||
pdfOrganiser.mode=Mode
|
||||
pdfOrganiser.mode.1=Custom Page Order
|
||||
pdfOrganiser.mode.2=Reverse Order
|
||||
pdfOrganiser.mode.3=Duplex Sort
|
||||
pdfOrganiser.mode.4=Booklet Sort
|
||||
pdfOrganiser.mode.5=Side Stitch Booklet Sort
|
||||
pdfOrganiser.mode.6=Odd-Even Split
|
||||
pdfOrganiser.mode.7=Remove First
|
||||
pdfOrganiser.mode.8=Remove Last
|
||||
pdfOrganiser.mode.9=Remove First and Last
|
||||
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1)
|
||||
pdfOrganiser.mode=Modo
|
||||
pdfOrganiser.mode.1=Orden de páginas personalizado
|
||||
pdfOrganiser.mode.2=Orden inverso
|
||||
pdfOrganiser.mode.3=Ordenar dúplex
|
||||
pdfOrganiser.mode.4=Ordenar folleto
|
||||
pdfOrganiser.mode.5=Orden de folleto de encuadernado lateral
|
||||
pdfOrganiser.mode.6=División par-impar
|
||||
pdfOrganiser.mode.7=Quitar primera
|
||||
pdfOrganiser.mode.8=Quitar última
|
||||
pdfOrganiser.mode.9=Quitar primera y última
|
||||
pdfOrganiser.placeholder=(por ej., 1,3,2 o 4-8,2,10-12 o 2n-1)
|
||||
|
||||
|
||||
#multiTool
|
||||
multiTool.title=Multi-herramienta PDF
|
||||
multiTool.header=Multi-herramienta PDF
|
||||
multiTool.uploadPrompts=Please Upload PDF
|
||||
multiTool.uploadPrompts=Por favor, cargue PDF
|
||||
|
||||
#view pdf
|
||||
viewPdf.title=Ver PDF
|
||||
|
@ -891,8 +901,8 @@ watermark.selectText.7=Opacidad (0% - 100%):
|
|||
watermark.selectText.8=Tipo de marca de agua:
|
||||
watermark.selectText.9=Imagen de marca de agua:
|
||||
watermark.submit=Añadir marca de agua
|
||||
watermark.type.1=Text
|
||||
watermark.type.2=Image
|
||||
watermark.type.1=Texto
|
||||
watermark.type.2=Imagen
|
||||
|
||||
|
||||
#Change permissions
|
||||
|
@ -944,7 +954,8 @@ pdfToPDFA.title=PDF a PDF/A
|
|||
pdfToPDFA.header=PDF a PDF/A
|
||||
pdfToPDFA.credit=Este servicio usa OCRmyPDF para la conversión a PDF/A
|
||||
pdfToPDFA.submit=Convertir
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.tip=Actualmente no funciona para múltiples entrada a la vez
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1039,15 @@ split-by-sections.vertical.placeholder=Introduzca el número de divisiones verti
|
|||
split-by-sections.submit=Dividir PDF
|
||||
split-by-sections.merge=Unir en Un PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Imprimir archivo
|
||||
printFile.header=Imprimir archivo en la impresora
|
||||
printFile.selectText.1=Seleccionar archivo para imprimir
|
||||
printFile.selectText.2=Introducir nombre de la impresora
|
||||
printFile.submit=Imprimir
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licencias
|
||||
licenses.title=Licencias de terceros
|
||||
|
@ -1038,15 +1058,15 @@ licenses.license=Licencia
|
|||
|
||||
|
||||
# error
|
||||
error.sorry=Sorry for the issue!
|
||||
error.needHelp=Need help / Found an issue?
|
||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
|
||||
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
|
||||
error.404.1=We can't seem to find the page you're looking for.
|
||||
error.404.2=Something went wrong
|
||||
error.github=Submit a ticket on GitHub
|
||||
error.showStack=Show Stack Trace
|
||||
error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
error.sorry=¡Perdón por el fallo!
|
||||
error.needHelp=Necesita ayuda / Encontró un fallo?
|
||||
error.contactTip=Si sigue experimentando errores, no dude en contactarnos para solicitar soporte. Puede enviarnos un ticket en la página de GitHub o contactarnos mediante Discord:
|
||||
error.404.head=404 - Página no encontrada | Ups, tropezamos con el código!
|
||||
error.404.1=Parece que no podemos encontrar la página que está buscando.
|
||||
error.404.2=Algo salió mal
|
||||
error.github=Envíe un ticket en GitHub
|
||||
error.showStack=Mostrar seguimiento de pila
|
||||
error.copyStack=Mostrar seguimiento de pila
|
||||
error.githubSubmit=GitHub - Enviar un ticket
|
||||
error.discordSubmit=Discord - Enviar mensaje de soporte
|
||||
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Ezin da uneko erabiltzailearen rola jaitsi
|
||||
downgradeCurrentUserLongMessage=Ezin da uneko erabiltzailearen rola jaitsi. Beraz, oraingo erabiltzailea ez da erakutsiko.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Ezarpenak
|
||||
settings.update=Eguneratze eskuragarria
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Aplikazioaren bertsioa:
|
||||
settings.downloadOption.title=Hautatu deskargatzeko aukera (fitxategi bakarra deskargatzeko ZIP gabe):
|
||||
settings.downloadOption.1=Ireki leiho berean
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Deskargatu fitxategia
|
|||
settings.zipThreshold=ZIP fitxategiak deskargatutako fitxategi kopurua gainditzen denean
|
||||
settings.signOut=Saioa itxi
|
||||
settings.accountSettings=Kontuaren ezarpenak
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Gorde Erabiltzailea
|
||||
adminUserSettings.changeUserRole=Erabiltzailearen rola aldatu
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Oroitu nazazu
|
|||
login.invalid=Okerreko erabiltzaile izena edo pasahitza.
|
||||
login.locked=Zure kontua blokeatu egin da.
|
||||
login.signinTitle=Mesedez, hasi saioa
|
||||
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDFa PDF/A bihurtu
|
|||
pdfToPDFA.credit=Zerbitzu honek OCRmyPDF erabiltzen du PDFak PDF/A bihurtzeko
|
||||
pdfToPDFA.submit=Bihurtu
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Le nouveau nom d’utilisateur existe déjà.
|
|||
invalidUsernameMessage=Nom d’utilisateur invalide, le nom d’utilisateur ne peut contenir que des chiffres et des lettres.
|
||||
deleteCurrentUserMessage=Impossible de supprimer l’utilisateur actuellement connecté.
|
||||
deleteUsernameExistsMessage=Le nom d’utilisateur n’existe pas et ne peut pas être supprimé.
|
||||
downgradeCurrentUserMessage=Impossible de rétrograder le rôle de l'utilisateur actuel
|
||||
downgradeCurrentUserLongMessage=Impossible de rétrograder le rôle de l'utilisateur actuel. Par conséquent, l'utilisateur actuel ne sera pas affiché.
|
||||
error=Erreur
|
||||
oops=Oups !
|
||||
help=Aide
|
||||
|
@ -70,7 +72,6 @@ sponsor=Sponsor
|
|||
info=Info
|
||||
|
||||
|
||||
|
||||
###############
|
||||
# Pipeline #
|
||||
###############
|
||||
|
@ -117,6 +118,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Paramètres
|
||||
settings.update=Mise à jour disponible
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Version de l’application :
|
||||
settings.downloadOption.title=Choisissez l’option de téléchargement (pour les téléchargements à fichier unique non ZIP) :
|
||||
settings.downloadOption.1=Ouvrir dans la même fenêtre
|
||||
|
@ -125,8 +127,9 @@ settings.downloadOption.3=Télécharger le fichier
|
|||
settings.zipThreshold=Compresser les fichiers en ZIP lorsque le nombre de fichiers téléchargés dépasse
|
||||
settings.signOut=Déconnexion
|
||||
settings.accountSettings=Paramètres du compte
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Modifiez vos identifiants
|
||||
changeCreds.header=Mettez à jour vos identifiants de connexion
|
||||
|
@ -176,6 +179,7 @@ adminUserSettings.demoUser=Demo User (Paramètres par défaut)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’utilisateur/mot de passe lors de la connexion
|
||||
adminUserSettings.submit=Ajouter
|
||||
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +444,8 @@ login.rememberme=Se souvenir de moi
|
|||
login.invalid=Nom d’utilisateur ou mot de passe invalide.
|
||||
login.locked=Votre compte a été verrouillé.
|
||||
login.signinTitle=Veuillez vous connecter
|
||||
login.ssoSignIn=Se connecter via l'authentification unique
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Création automatique d'utilisateur désactivée
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +951,7 @@ pdfToPDFA.header=PDF en PDF/A
|
|||
pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion en PDF/A.
|
||||
pdfToPDFA.submit=Convertir
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1035,15 @@ split-by-sections.vertical.placeholder=Entrer le nombre de divisions verticales
|
|||
split-by-sections.submit=Diviser le PDF
|
||||
split-by-sections.merge=Fusionner en un seul PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licences
|
||||
licenses.title=Licences tierces
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=नया उपयोगकर्ता नाम पह
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता
|
||||
downgradeCurrentUserLongMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता। इसलिए, वर्तमान उपयोगकर्ता को नहीं दिखाया जाएगा।
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=सेटिंग्स
|
||||
settings.update=अपडेट उपलब्ध है
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=ऐप संस्करण:
|
||||
settings.downloadOption.title=डाउनलोड विकल्प चुनें (एकल फ़ाइल गैर-ज़िप डाउनलोड के लिए):
|
||||
settings.downloadOption.1=एक ही विंडो में खोलें
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=फ़ाइल डाउनलोड करें
|
|||
settings.zipThreshold=जब डाउनलोड की गई फ़ाइलों की संख्या सीमा से अधिक हो
|
||||
settings.signOut=साइन आउट
|
||||
settings.accountSettings=खाता सेटिंग्स
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=क्रेडेंशियल बदलें
|
||||
changeCreds.header=अपना खाता विवरण अपडेट करें
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=उपयोगकर्ता को लॉगिन पर उपयोगकर्ता नाम/पासवर्ड बदलने के लिए मजबूर करें
|
||||
adminUserSettings.submit=उपयोगकर्ता को सहेजें
|
||||
adminUserSettings.changeUserRole=यूज़र की भूमिका बदलें
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=मुझे याद रखें
|
|||
login.invalid=अमान्य उपयोगकर्ता नाम या पासवर्ड।
|
||||
login.locked=आपका खाता लॉक कर दिया गया है।
|
||||
login.signinTitle=कृपया साइन इन करें
|
||||
login.ssoSignIn=सिंगल साइन - ऑन के ज़रिए लॉग इन करें
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 ऑटो - क्रिएट यूज़र अक्षम किया गया
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF से PDF/A में
|
|||
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए OCRmyPDF का उपयोग किया जाता है।
|
||||
pdfToPDFA.submit=परिवर्तित करें
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=लंबवत विभाजन की
|
|||
split-by-sections.submit=PDF को विभाजित करें
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Az új felhasználónév már létezik.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=A jelenlegi felhasználó szerepkörét nem lehet visszaminősíteni
|
||||
downgradeCurrentUserLongMessage=Az aktuális felhasználó szerepkörét nem lehet visszaminősíteni. Ezért az aktuális felhasználó nem jelenik meg.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Beállítások
|
||||
settings.update=Frisítés elérhető
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=App Verzió:
|
||||
settings.downloadOption.title=Válassza ki a letöltési lehetőséget (Egyetlen fájl esetén a nem tömörített letöltésekhez):
|
||||
settings.downloadOption.1=Nyissa meg ugyanabban az ablakban
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Töltse le a fájlt
|
|||
settings.zipThreshold=Fájlok tömörítése, ha a letöltött fájlok száma meghaladja
|
||||
settings.signOut=Kijelentkezés
|
||||
settings.accountSettings=Fiókbeállítások
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Hitelesítés megváltoztatása
|
||||
changeCreds.header=Frissítse fiókadatait
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Kényszerítse a felhasználót a felhasználónév/jelszó megváltoztatására bejelentkezéskor
|
||||
adminUserSettings.submit=Felhasználó mentése
|
||||
adminUserSettings.changeUserRole=Felhasználó szerepkörének módosítása
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Emlékezz rám
|
|||
login.invalid=Érvénytelen felhasználónév vagy jelszó!
|
||||
login.locked=A fiókja zárolva lett!
|
||||
login.signinTitle=Kérjük, jelentkezzen be!
|
||||
login.ssoSignIn=Bejelentkezés egyszeri bejelentkezéssel
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Felhasználó automatikus létrehozása letiltva
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF >> PDF/A
|
|||
pdfToPDFA.credit=Ez a szolgáltatás az OCRmyPDF-t használja a PDF/A konverzióhoz
|
||||
pdfToPDFA.submit=Konvertálás
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Adja meg a függőleges szakaszok számá
|
|||
split-by-sections.submit=Felosztás
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Nama pengguna baru sudah ada.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Tidak dapat menurunkan peran pengguna saat ini
|
||||
downgradeCurrentUserLongMessage=Tidak dapat menurunkan peran pengguna saat ini. Oleh karena itu, pengguna saat ini tidak akan ditampilkan.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Pengaturan
|
||||
settings.update=Pembaruan tersedia
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Versi Aplikasi:
|
||||
settings.downloadOption.title=Pilih opsi unduhan (Untuk unduhan berkas tunggal non zip):
|
||||
settings.downloadOption.1=Buka di jendela yang sama
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Unduh berkas
|
|||
settings.zipThreshold=Berkas zip ketika jumlah berkas yang diunduh melebihi
|
||||
settings.signOut=Keluar
|
||||
settings.accountSettings=Pengaturan Akun
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Ubah Kredensial
|
||||
changeCreds.header=Perbarui Detail Akun Anda
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata sandi saat masuk
|
||||
adminUserSettings.submit=Simpan Pengguna
|
||||
adminUserSettings.changeUserRole=Ubah Peran Pengguna
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Ingat saya
|
|||
login.invalid=Nama pengguna atau kata sandi tidak valid.
|
||||
login.locked=Akun Anda telah dikunci.
|
||||
login.signinTitle=Silakan masuk
|
||||
login.ssoSignIn=Masuk melalui Single Sign - on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF ke PDF/A
|
|||
pdfToPDFA.credit=Layanan ini menggunakan OCRmyPDF untuk konversi PDF/A.
|
||||
pdfToPDFA.submit=Konversi
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Input angka untuk pembagian vertikal
|
|||
split-by-sections.submit=Pisahkan PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Il nuovo nome utente esiste già.
|
|||
invalidUsernameMessage=Nome utente non valido, il nome utente deve contenere solo caratteri alfabetici e numeri.
|
||||
deleteCurrentUserMessage=Impossibile eliminare l'utente attualmente connesso.
|
||||
deleteUsernameExistsMessage=Il nome utente non esiste e non può essere eliminato.
|
||||
downgradeCurrentUserMessage=Impossibile declassare il ruolo dell'utente corrente
|
||||
downgradeCurrentUserLongMessage=Impossibile declassare il ruolo dell'utente corrente. Pertanto, l'utente corrente non verrà visualizzato.
|
||||
error=Errore
|
||||
oops=Oops!
|
||||
help=Aiuto
|
||||
|
@ -81,6 +83,7 @@ pipeline.defaultOption=Personalizzato
|
|||
pipeline.submitButton=Invia
|
||||
pipeline.help=Aiuto sulla pipeline
|
||||
pipeline.scanHelp=Aiuto per la scansione delle cartelle
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
######################
|
||||
|
@ -116,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Impostazioni
|
||||
settings.update=Aggiornamento disponibile
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Versione App:
|
||||
settings.downloadOption.title=Scegli opzione di download (Per file singoli non compressi):
|
||||
settings.downloadOption.1=Apri in questa finestra
|
||||
|
@ -124,8 +128,9 @@ settings.downloadOption.3=Scarica file
|
|||
settings.zipThreshold=Comprimi file in .zip quando il numero di download supera
|
||||
settings.signOut=Logout
|
||||
settings.accountSettings=Impostazioni Account
|
||||
|
||||
|
||||
settings.bored.help=Abilita easter egg game
|
||||
settings.cacheInputs.name=Salva gli input del modulo
|
||||
settings.cacheInputs.help=Abilitare per memorizzare gli input utilizzati in precedenza per esecuzioni future
|
||||
|
||||
changeCreds.title=Cambia credenziali
|
||||
changeCreds.header=Aggiorna i dettagli del tuo account
|
||||
|
@ -175,6 +180,7 @@ adminUserSettings.demoUser=Utente demo (nessuna impostazione personalizzata)
|
|||
adminUserSettings.internalApiUser=API utente interna
|
||||
adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso
|
||||
adminUserSettings.submit=Salva utente
|
||||
adminUserSettings.changeUserRole=Cambia il ruolo dell'utente
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -193,7 +199,7 @@ multiTool.tags=Strumento multiplo,operazione multipla,interfaccia utente,trascin
|
|||
|
||||
home.merge.title=Unisci
|
||||
home.merge.desc=Unisci facilmente più PDF in uno.
|
||||
merge.tags=unione,operazioni sulla pagina,back end,lato server
|
||||
merge.tags=unione,operazioni sulla pagina,back-end,lato server
|
||||
|
||||
home.split.title=Dividi
|
||||
home.split.desc=Dividi un singolo PDF in più documenti.
|
||||
|
@ -439,6 +445,8 @@ login.rememberme=Ricordami
|
|||
login.invalid=Nome utente o password errati.
|
||||
login.locked=Il tuo account è stato bloccato.
|
||||
login.signinTitle=Per favore accedi
|
||||
login.ssoSignIn=Accedi tramite Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -648,7 +656,7 @@ removeBlanks.submit=Rimuovi
|
|||
|
||||
#removeAnnotations
|
||||
removeAnnotations.title=Rimuovi Annotazioni
|
||||
removeAnnotations.header=Remuovi Annotazioni
|
||||
removeAnnotations.header=Rimuovi Annotazioni
|
||||
removeAnnotations.submit=Rimuovi
|
||||
|
||||
|
||||
|
@ -944,6 +952,7 @@ pdfToPDFA.header=Da PDF a PDF/A
|
|||
pdfToPDFA.credit=Questo servizio utilizza OCRmyPDF per la conversione in PDF/A.
|
||||
pdfToPDFA.submit=Converti
|
||||
pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente
|
||||
pdfToPDFA.outputFormat=Formato di output
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1027,6 +1036,15 @@ split-by-sections.vertical.placeholder=Inserire il numero di divisioni verticali
|
|||
split-by-sections.submit=Dividi PDF
|
||||
split-by-sections.merge=Unisci in un unico PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Stampa file
|
||||
printFile.header=Stampa file su stampante
|
||||
printFile.selectText.1=Seleziona file da stampare
|
||||
printFile.selectText.2=Inserire il nome della stampante
|
||||
printFile.submit=Stampare
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenze
|
||||
licenses.title=Licenze di terze parti
|
||||
|
|
|
@ -57,16 +57,18 @@ usernameExistsMessage=新しいユーザー名はすでに存在します。
|
|||
invalidUsernameMessage=ユーザー名が無効です。ユーザー名にはアルファベットと数字のみを使用してください。
|
||||
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
|
||||
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
goHomepage=Go to Homepage
|
||||
joinDiscord=Join our Discord server
|
||||
seeDockerHub=See Docker Hub
|
||||
visitGithub=Visit Github Repository
|
||||
donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
|
||||
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
|
||||
error=エラー
|
||||
oops=おっと!
|
||||
help=ヘルプ
|
||||
goHomepage=ホームページへ移動
|
||||
joinDiscord=Discordサーバーに参加する
|
||||
seeDockerHub=Docker Hubを見る
|
||||
visitGithub=Githubリポジトリを訪問する
|
||||
donate=寄付する
|
||||
color=色
|
||||
sponsor=スポンサー
|
||||
info=Info
|
||||
|
||||
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=設定
|
||||
settings.update=利用可能なアップデート
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Appバージョン:
|
||||
settings.downloadOption.title=ダウンロードオプション (zip以外の単一ファイル):
|
||||
settings.downloadOption.1=同じウィンドウで開く
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=ファイルをダウンロード
|
|||
settings.zipThreshold=このファイル数を超えたときにファイルを圧縮する
|
||||
settings.signOut=サインアウト
|
||||
settings.accountSettings=アカウント設定
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=資格情報の変更
|
||||
changeCreds.header=アカウントの詳細を更新する
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=デモユーザー (カスタム設定なし)
|
|||
adminUserSettings.internalApiUser=内部APIユーザー
|
||||
adminUserSettings.forceChange=ログイン時にユーザー名/パスワードを強制的に変更する
|
||||
adminUserSettings.submit=ユーザーの保存
|
||||
adminUserSettings.changeUserRole=ユーザーの役割を変更する
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=サインイン状態を記憶する
|
|||
login.invalid=ユーザー名かパスワードが無効です。
|
||||
login.locked=あなたのアカウントはロックされています。
|
||||
login.signinTitle=サインインしてください
|
||||
login.ssoSignIn=シングルサインオンでログイン
|
||||
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDFをPDF/Aに変換
|
|||
pdfToPDFA.credit=本サービスはPDF/Aの変換にOCRmyPDFを使用しています。
|
||||
pdfToPDFA.submit=変換
|
||||
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=垂直方向の分割数を選択
|
|||
split-by-sections.submit=分割
|
||||
split-by-sections.merge=1 つの PDF に結合するかどうか
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=ライセンス
|
||||
licenses.title=サードパーティライセンス
|
||||
|
@ -1038,15 +1055,15 @@ licenses.license=ライセンス
|
|||
|
||||
|
||||
# error
|
||||
error.sorry=Sorry for the issue!
|
||||
error.needHelp=Need help / Found an issue?
|
||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
|
||||
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
|
||||
error.404.1=We can't seem to find the page you're looking for.
|
||||
error.404.2=Something went wrong
|
||||
error.github=Submit a ticket on GitHub
|
||||
error.showStack=Show Stack Trace
|
||||
error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
error.sorry=問題が発生したことをお詫び申し上げます!
|
||||
error.needHelp=助けが必要/問題が見つかりましたか?
|
||||
error.contactTip=まだ問題が解決していない場合は、お手数ですが、GitHubページでチケットを提出するか、Discordで私たちに連絡してください:
|
||||
error.404.head=404 - ページが見つかりません | おっと、コードでつまずきました!
|
||||
error.404.1=あなたが探しているページが見つかりません。
|
||||
error.404.2=何か問題が発生しました
|
||||
error.github=GitHubでチケットを提出
|
||||
error.showStack=スタックトレースを表示
|
||||
error.copyStack=スタックトレースをコピー
|
||||
error.githubSubmit=GitHub - チケットを提出
|
||||
error.discordSubmit=Discord - サポート投稿を提出
|
||||
|
||||
|
|
|
@ -57,17 +57,20 @@ usernameExistsMessage=새 사용자명이 이미 존재합니다.
|
|||
invalidUsernameMessage=사용자 이름이 잘못되었습니다. 사용자 이름에는 알파벳 문자와 숫자만 포함되어야 합니다.
|
||||
deleteCurrentUserMessage=현재 로그인한 사용자를 삭제할 수 없습니다.
|
||||
deleteUsernameExistsMessage=사용자 이름이 존재하지 않으며 삭제할 수 없습니다.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
goHomepage=Go to Homepage
|
||||
joinDiscord=Join our Discord server
|
||||
seeDockerHub=See Docker Hub
|
||||
visitGithub=Visit Github Repository
|
||||
donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
|
||||
info=Info
|
||||
downgradeCurrentUserMessage=현재 사용자의 역할을 다운그레이드할 수 없습니다
|
||||
downgradeCurrentUserLongMessage=현재 사용자의 역할을 다운그레이드할 수 없습니다. 따라서 현재 사용자는 표시되지 않습니다.
|
||||
error=오류
|
||||
oops=어머나!
|
||||
help=도움말
|
||||
goHomepage=홈페이지로 이동
|
||||
joinDiscord=Discord 서버에 참여하기
|
||||
seeDockerHub=Docker Hub에서 확인하기
|
||||
visitGithub=GitHub 저장소 방문하기
|
||||
donate=기부하기
|
||||
color=색상
|
||||
sponsor=스폰서
|
||||
|
||||
|
||||
|
||||
|
@ -117,6 +120,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=설정
|
||||
settings.update=업데이트 가능
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=앱 버전:
|
||||
settings.downloadOption.title=다운로드 옵션 선택 (zip 파일이 아닌 단일 파일 다운로드 시):
|
||||
settings.downloadOption.1=현재 창에서 열기
|
||||
|
@ -125,8 +129,9 @@ settings.downloadOption.3=다운로드
|
|||
settings.zipThreshold=다운로드한 파일 수가 초과된 경우 파일 압축하기
|
||||
settings.signOut=로그아웃
|
||||
settings.accountSettings=계정 설정
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=계정 정보 변경
|
||||
changeCreds.header=계정 정보 업데이트
|
||||
|
@ -176,6 +181,7 @@ adminUserSettings.demoUser=데모 사용자(사용자 지정 설정 없음)
|
|||
adminUserSettings.internalApiUser=내부 API 사용자
|
||||
adminUserSettings.forceChange=다음 로그인 때 사용자명과 비밀번호를 변경하도록 강제
|
||||
adminUserSettings.submit=사용자 저장
|
||||
adminUserSettings.changeUserRole=사용자의 역할 변경
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +446,8 @@ login.rememberme=로그인 유지
|
|||
login.invalid=사용자 이름이나 비밀번호가 틀립니다.
|
||||
login.locked=계정이 잠겼습니다.
|
||||
login.signinTitle=로그인해 주세요.
|
||||
login.ssoSignIn=싱글사인온을 통한 로그인
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 사용자 자동 생성 비활성화됨
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +953,7 @@ pdfToPDFA.header=PDF 문서를 PDF/A로 변환
|
|||
pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 OCRmyPDF 문서를 사용합니다.
|
||||
pdfToPDFA.submit=변환
|
||||
pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다.
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1037,15 @@ split-by-sections.vertical.placeholder=수직 분할 수를 입력합니다
|
|||
split-by-sections.submit=PDF 분할
|
||||
split-by-sections.merge=하나의 PDF로 병합
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=라이센스
|
||||
licenses.title=제3자 라이선스
|
||||
|
@ -1038,15 +1056,15 @@ licenses.license=라이센스
|
|||
|
||||
|
||||
# error
|
||||
error.sorry=Sorry for the issue!
|
||||
error.needHelp=Need help / Found an issue?
|
||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
|
||||
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
|
||||
error.404.1=We can't seem to find the page you're looking for.
|
||||
error.404.2=Something went wrong
|
||||
error.github=Submit a ticket on GitHub
|
||||
error.showStack=Show Stack Trace
|
||||
error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
error.sorry=문제를 끼친 점 죄송합니다!
|
||||
error.needHelp=도움이 필요하신가요 / 문제가 있으신가요?
|
||||
error.contactTip=여전히 문제가 해결되지 않는다면 망설이지 마시고 도움을 요청하십시오. GitHub 페이지에서 티켓을 제출하거나 Discord를 통해 우리에게 연락하실 수 있습니다:
|
||||
error.404.head=404 - 페이지를 찾을 수 없습니다 | 이런, 코드에 걸려 넘어졌어요!
|
||||
error.404.1=원하시는 페이지를 찾을 수가 없네요.
|
||||
error.404.2=문제가 발생했습니다
|
||||
error.github=GitHub에서 티켓 제출
|
||||
error.showStack=스택 추적 보기
|
||||
error.copyStack=스택 추적 복사
|
||||
error.githubSubmit=GitHub - 티켓 제출
|
||||
error.discordSubmit=Discord - 문의 게시
|
||||
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Nieuwe gebruikersnaam bestaat al.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Kan de rol van de huidige gebruiker niet downgraden
|
||||
downgradeCurrentUserLongMessage=Kan de rol van de huidige gebruiker niet downgraden. Huidige gebruiker wordt dus niet weergegeven.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Instellingen
|
||||
settings.update=Update beschikbaar
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=App versie:
|
||||
settings.downloadOption.title=Kies download optie (Voor enkelvoudige bestanddownloads zonder zip):
|
||||
settings.downloadOption.1=Open in hetzelfde venster
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Download bestand
|
|||
settings.zipThreshold=Bestanden zippen wanneer het aantal gedownloade bestanden meer is dan
|
||||
settings.signOut=Uitloggen
|
||||
settings.accountSettings=Account instellingen
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Inloggegevens wijzigen
|
||||
changeCreds.header=Werk je accountgegevens bij
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demogebruiker (geen aangepaste instellingen)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Forceer gebruiker om gebruikersnaam/wachtwoord te wijzigen bij inloggen
|
||||
adminUserSettings.submit=Gebruiker opslaan
|
||||
adminUserSettings.changeUserRole=De rol van de gebruiker wijzigen
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Onthoud mij
|
|||
login.invalid=Ongeldige gebruikersnaam of wachtwoord.
|
||||
login.locked=Je account is geblokkeerd.
|
||||
login.signinTitle=Gelieve in te loggen
|
||||
login.ssoSignIn=Inloggen via Single Sign-on
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF naar PDF/A
|
|||
pdfToPDFA.credit=Deze service gebruikt OCRmyPDF voor PDF/A-conversie
|
||||
pdfToPDFA.submit=Converteren
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Voer het aantal verticale secties in
|
|||
split-by-sections.submit=PDF splitsen
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenties
|
||||
licenses.title=Licenties van derden
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Nie można obniżyć roli bieżącego użytkownika
|
||||
downgradeCurrentUserLongMessage=Nie można obniżyć roli bieżącego użytkownika. W związku z tym bieżący użytkownik nie zostanie wyświetlony.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Ustawienia
|
||||
settings.update=Dostępna aktualizacja
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Wersia aplikacji:
|
||||
settings.downloadOption.title=Wybierz opcję pobierania (w przypadku pobierania pojedynczych plików innych niż ZIP):
|
||||
settings.downloadOption.1=Otwórz w tym samym oknie
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Pobierz plik
|
|||
settings.zipThreshold=Spakuj pliki, gdy liczba pobranych plików przekroczy
|
||||
settings.signOut=Sign Out
|
||||
settings.accountSettings=Account Settings
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Zmień rolę użytkownika
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Remember me
|
|||
login.invalid=Invalid username or password.
|
||||
login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
||||
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF na PDF/A
|
|||
pdfToPDFA.credit=Ta usługa używa OCRmyPDF do konwersji PDF/A
|
||||
pdfToPDFA.submit=Konwertuj
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Não é possível fazer downgrade da função do usuário atual
|
||||
downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do usuário atual. Portanto, o usuário atual não será mostrado.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Configurações
|
||||
settings.update=Atualização disponível
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Versão do aplicativo:
|
||||
settings.downloadOption.title=Escolha a opção de download (para downloads não compactados de arquivo único):
|
||||
settings.downloadOption.1=Abrir na mesma janela
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=⇬ Fazer download do arquivo
|
|||
settings.zipThreshold=Compactar arquivos quando o número de arquivos baixados exceder
|
||||
settings.signOut=Sign Out
|
||||
settings.accountSettings=Account Settings
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Alterar Função de Usuário
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Remember me
|
|||
login.invalid=Invalid username or password.
|
||||
login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Criar Usuário Desativado
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF para PDF/A
|
|||
pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A
|
||||
pdfToPDFA.submit=Converter
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Esse utilizador já existe.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Não é possível fazer downgrade da função do utilizador atual
|
||||
downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do utilizador atual. Portanto, o utilizador atual não será mostrado.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Configurações
|
||||
settings.update=Atualização disponível
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Versão da aplicação:
|
||||
settings.downloadOption.title=Escolha a opção de download (para downloads não compactados de ficheiro único):
|
||||
settings.downloadOption.1=Abrir na mesma janela
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=⇬ Fazer download do ficheiro
|
|||
settings.zipThreshold=Compactar ficheiros quando o número de ficheiros baixados exceder
|
||||
settings.signOut=Terminar Sessão
|
||||
settings.accountSettings=Configuração de Conta
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Alterar senha
|
||||
changeCreds.header=Alterar dados da sua conta
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Alterar usuário
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Lembrar dados
|
|||
login.invalid=Utilizador ou senha inválidos.
|
||||
login.locked=A sua conta foi bloqueada.
|
||||
login.signinTitle=Introduza os seus dados de acesso
|
||||
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Criação Automática de Utilizador Desativada
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF para PDF/A
|
|||
pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A
|
||||
pdfToPDFA.submit=Converter
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Introduza o número de divisões verticai
|
|||
split-by-sections.submit=Dividir PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenças
|
||||
licenses.title=Licenças de terceiros
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Rolul utilizatorului curent nu poate fi retrogradat
|
||||
downgradeCurrentUserLongMessage=Rolul utilizatorului curent nu poate fi retrogradat. Prin urmare, utilizatorul curent nu va fi afișat.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Setări
|
||||
settings.update=Actualizare disponibilă
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Versiune aplicație:
|
||||
settings.downloadOption.title=Alege opțiunea de descărcare (pentru descărcarea unui singur fișier non-zip):
|
||||
settings.downloadOption.1=Deschide în aceeași fereastră
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Descarcă fișierul
|
|||
settings.zipThreshold=Împachetează fișierele când numărul de fișiere descărcate depășește
|
||||
settings.signOut=Sign Out
|
||||
settings.accountSettings=Account Settings
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Schimbați rolul utilizatorului
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Remember me
|
|||
login.invalid=Invalid username or password.
|
||||
login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Conectare prin conectare unică
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF către PDF/A
|
|||
pdfToPDFA.credit=Acest serviciu utilizează OCRmyPDF pentru conversia în PDF/A
|
||||
pdfToPDFA.submit=Convert
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,17 +57,20 @@ usernameExistsMessage=Новое имя пользователя уже суще
|
|||
invalidUsernameMessage=Недопустимое имя пользователя, Имя пользователя должно содержать только буквы алфавита и цифры.
|
||||
deleteCurrentUserMessage=Невозможно удалить пользователя, вошедшего в систему.
|
||||
deleteUsernameExistsMessage=Имя пользователя не существует и не может быть удалено.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
goHomepage=Go to Homepage
|
||||
joinDiscord=Join our Discord server
|
||||
seeDockerHub=See Docker Hub
|
||||
visitGithub=Visit Github Repository
|
||||
donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
downgradeCurrentUserMessage=Невозможно понизить роль текущего пользователя
|
||||
downgradeCurrentUserLongMessage=Невозможно понизить роль текущего пользователя. Следовательно, текущий пользователь не будет отображаться.
|
||||
error=Ошибка
|
||||
oops=Ой!
|
||||
help=Помощь
|
||||
goHomepage=Перейти на главную страницу
|
||||
joinDiscord=Присоединиться к нашему серверу Discord
|
||||
seeDockerHub=Посмотреть в Docker Hub
|
||||
visitGithub=Посетить репозиторий на GitHub
|
||||
donate=Пожертвовать
|
||||
color=Цвет
|
||||
sponsor=Спонсор
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -117,6 +120,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Настройки
|
||||
settings.update=Доступно обновление
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Версия приложения:
|
||||
settings.downloadOption.title=Выберите вариант загрузки (для загрузки одного файла без zip):
|
||||
settings.downloadOption.1=Открыть в том же окне
|
||||
|
@ -125,8 +129,9 @@ settings.downloadOption.3=Загрузить файл
|
|||
settings.zipThreshold=Zip-файлы, когда количество загруженных файлов превышает
|
||||
settings.signOut=Выйти
|
||||
settings.accountSettings=Настройки аккаунта
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Изменить учетные данные
|
||||
changeCreds.header=Обновите данные вашей учетной записи
|
||||
|
@ -176,6 +181,7 @@ adminUserSettings.demoUser=Демо-пользователь (без настр
|
|||
adminUserSettings.internalApiUser=Внутренний пользователь API
|
||||
adminUserSettings.forceChange=Просить пользователя изменить пароль при входе в систему
|
||||
adminUserSettings.submit=Сохранить пользователя
|
||||
adminUserSettings.changeUserRole=Изменить роль пользователя
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +446,8 @@ login.rememberme=Запомнить меня
|
|||
login.invalid=Недействительное имя пользователя или пароль.
|
||||
login.locked=Ваша учетная запись заблокирована.
|
||||
login.signinTitle=Пожалуйста, войдите
|
||||
login.ssoSignIn=Вход через единый вход
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматическое создание пользователя отключено
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +953,7 @@ pdfToPDFA.header=PDF в PDF/A
|
|||
pdfToPDFA.credit=Этот сервис использует OCRmyPDF для преобразования PDF/A
|
||||
pdfToPDFA.submit=Конвертировать
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1037,15 @@ split-by-sections.vertical.placeholder=Введите количество ве
|
|||
split-by-sections.submit=Разделить PDF
|
||||
split-by-sections.merge=Объединить в один PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Лицензии
|
||||
licenses.title=Лицензии от третьих сторон
|
||||
|
@ -1038,15 +1056,15 @@ licenses.license=Лицензия
|
|||
|
||||
|
||||
# error
|
||||
error.sorry=Sorry for the issue!
|
||||
error.needHelp=Need help / Found an issue?
|
||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
|
||||
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
|
||||
error.404.1=We can't seem to find the page you're looking for.
|
||||
error.404.2=Something went wrong
|
||||
error.github=Submit a ticket on GitHub
|
||||
error.showStack=Show Stack Trace
|
||||
error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
error.sorry=Извините за проблему!
|
||||
error.needHelp=Нужна помощь / Нашли проблему?
|
||||
error.contactTip=Если у вас все еще есть проблемы, не стесняйтесь обращаться к нам за помощью. Вы можете отправить заявку на нашей странице GitHub или связаться с нами через Discord:
|
||||
error.404.head=404 - Страница не найдена | Ой, мы запутались в коде!
|
||||
error.404.1=Мы не можем найти страницу, которую вы ищете.
|
||||
error.404.2=Произошла ошибка
|
||||
error.github=Отправить заявку на GitHub
|
||||
error.showStack=Показать стек вызовов
|
||||
error.copyStack=Скопировать стек вызовов
|
||||
error.githubSubmit=GitHub - Отправить заявку
|
||||
error.discordSubmit=Discord - Отправить запрос в поддержку
|
||||
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Novi korisnik već postoji
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Nije moguće degradirati ulogu trenutnog korisnika
|
||||
downgradeCurrentUserLongMessage=Nije moguće unazaditi ulogu trenutnog korisnika. Dakle, trenutni korisnik neće biti prikazan.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Podešavanja
|
||||
settings.update=Dostupno ažuriranje
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Verzija aplikacije:
|
||||
settings.downloadOption.title=Odaberite opciju preuzimanja (Za preuzimanje pojedinačnih fajlova bez zip formata):
|
||||
settings.downloadOption.1=Otvori u istom prozoru
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Preuzmi fajl
|
|||
settings.zipThreshold=Zipuj fajlove kada pređe broj preuzetih fajlova
|
||||
settings.signOut=Odjava
|
||||
settings.accountSettings=Podešavanja naloga
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Promeni pristupne podatke
|
||||
changeCreds.header=Ažurirajte detalje svog naloga
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo korisnik (Bez prilagođenih podešavanja)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Prisili korisnika da promeni korisničko ime/lozinku pri prijavi
|
||||
adminUserSettings.submit=Sačuvaj korisnika
|
||||
adminUserSettings.changeUserRole=Promenite ulogu korisnika
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Zapamti me
|
|||
login.invalid=Neispravno korisničko ime ili lozinka.
|
||||
login.locked=Vaš nalog je zaključan.
|
||||
login.signinTitle=Molimo vas da se prijavite
|
||||
login.ssoSignIn=Prijavite se putem jedinstvene prijave
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 automatsko kreiranje korisnika je onemogućeno
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF u PDF/A
|
|||
pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za konverziju u PDF/A format
|
||||
pdfToPDFA.submit=Konvertuj
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Unesite broj vertikalnih podele
|
|||
split-by-sections.submit=Razdvoji PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
|
|||
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
|
||||
deleteCurrentUserMessage=Cannot delete currently logged in user.
|
||||
deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
|
||||
downgradeCurrentUserMessage=Kan inte nedgradera nuvarande användares roll
|
||||
downgradeCurrentUserLongMessage=Kan inte nedgradera nuvarande användares roll. Därför kommer den aktuella användaren inte att visas.
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Inställningar
|
||||
settings.update=Uppdatering tillgänglig
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=Appversion:
|
||||
settings.downloadOption.title=Välj nedladdningsalternativ (för nedladdning av en fil utan zip):
|
||||
settings.downloadOption.1=Öppnas i samma fönster
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Ladda ner fil
|
|||
settings.zipThreshold=Zip-filer när antalet nedladdade filer överskrider
|
||||
settings.signOut=Sign Out
|
||||
settings.accountSettings=Account Settings
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=Change Credentials
|
||||
changeCreds.header=Update Your Account Details
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
|
|||
adminUserSettings.internalApiUser=Internal API User
|
||||
adminUserSettings.forceChange=Force user to change password on login
|
||||
adminUserSettings.submit=Save User
|
||||
adminUserSettings.changeUserRole=Ändra användarens roll
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Remember me
|
|||
login.invalid=Invalid username or password.
|
||||
login.locked=Your account has been locked.
|
||||
login.signinTitle=Please sign in
|
||||
login.ssoSignIn=Logga in via enkel inloggning
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User inaktiverad
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF till PDF/A
|
|||
pdfToPDFA.credit=Denna tjänst använder OCRmyPDF för PDF/A-konvertering
|
||||
pdfToPDFA.submit=Konvertera
|
||||
pdfToPDFA.tip=Currently does not work for multiple inputs at once
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
|
|||
split-by-sections.submit=Split PDF
|
||||
split-by-sections.merge=Merge Into One PDF
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Licenses
|
||||
licenses.title=3rd Party Licenses
|
||||
|
|
|
@ -57,6 +57,8 @@ usernameExistsMessage=Yeni Kullanıcı Adı zaten var.
|
|||
invalidUsernameMessage=Geçersiz kullanıcı adı, Kullanıcı adı yalnızca alfabe karakterleri ve sayılar içermelidir.
|
||||
deleteCurrentUserMessage=Şu anda oturum açmış olan kullanıcı silinemiyor.
|
||||
deleteUsernameExistsMessage=Kullanıcı adı mevcut değil ve silinemez.
|
||||
downgradeCurrentUserMessage=Mevcut kullanıcının rolü düşürülemiyor
|
||||
downgradeCurrentUserLongMessage=Mevcut kullanıcının rolü düşürülemiyor. Bu nedenle, mevcut kullanıcı gösterilmeyecektir.
|
||||
error=Hata
|
||||
oops=Tüh!
|
||||
help=Yardım
|
||||
|
@ -66,32 +68,32 @@ seeDockerHub=Docker Hub'a bakın
|
|||
visitGithub=Github Deposunu Ziyaret Edin
|
||||
donate=Bağış Yapın
|
||||
color=Renk
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
sponsor=Bağış
|
||||
|
||||
|
||||
|
||||
###############
|
||||
# Pipeline #
|
||||
###############
|
||||
pipeline.header=Pipeline Menü (Beta)
|
||||
pipeline.header=Çoklu İşlemler Menü (Beta)
|
||||
pipeline.uploadButton=Upload edin
|
||||
pipeline.configureButton=Yapılandır
|
||||
pipeline.defaultOption=Özel
|
||||
pipeline.submitButton=Gönder
|
||||
pipeline.help=Pipeline Yardım
|
||||
pipeline.help=Çoklu İşlemler Yardım
|
||||
pipeline.scanHelp=Klasör Tarama Yardımı
|
||||
|
||||
######################
|
||||
# Pipeline Options #
|
||||
######################
|
||||
pipelineOptions.header=Pipeline Yapılandırma
|
||||
pipelineOptions.pipelineNameLabel=Pipeline İsim
|
||||
pipelineOptions.header=Çoklu İşlemler Yapılandırma
|
||||
pipelineOptions.pipelineNameLabel=Çoklu İşlemler İsim
|
||||
pipelineOptions.saveSettings=Ayarları Kaydet
|
||||
pipelineOptions.pipelineNamePrompt=Buraya isim girin
|
||||
pipelineOptions.selectOperation=İşlem Seçin
|
||||
pipelineOptions.addOperationButton=İşlem ekle
|
||||
pipelineOptions.pipelineHeader=Pipeline:
|
||||
pipelineOptions.pipelineHeader=Çoklu İşlemler:
|
||||
pipelineOptions.saveButton=İndir
|
||||
pipelineOptions.validateButton=Doğrula
|
||||
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=Ayarlar
|
||||
settings.update=Güncelleme mevcut
|
||||
settings.updateAvailable={0} mevcut kurulu sürümdür. Yeni bir sürüm ({1}) mevcuttur.
|
||||
settings.appVersion=Uygulama Sürümü:
|
||||
settings.downloadOption.title=İndirme seçeneği seçin (Zip olmayan tek dosya indirmeler için):
|
||||
settings.downloadOption.1=Aynı pencerede aç
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=Dosyayı indir
|
|||
settings.zipThreshold=İndirilen dosya sayısı şu değeri aştığında zip dosyası oluştur:
|
||||
settings.signOut=Oturumu Kapat
|
||||
settings.accountSettings=Hesap Ayarları
|
||||
|
||||
|
||||
settings.bored.help=Paskalya yumurtası oyunu etkinleştirir
|
||||
settings.cacheInputs.name=Form girdilerini kaydet
|
||||
settings.cacheInputs.help=Gelecekteki çalıştırmalar için önceden kullanılan girdileri saklamayı etkinleştirin
|
||||
|
||||
changeCreds.title=Giriş Bilgilerini Değiştir
|
||||
changeCreds.header=Hesap Detaylarınızı Güncelleyin
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=Demo Kullanıcısı (Özel ayar yok)
|
|||
adminUserSettings.internalApiUser=Dahili API Kullanıcısı
|
||||
adminUserSettings.forceChange=Kullanıcının girişte kullanıcı adı/şifre değiştirmesini zorla
|
||||
adminUserSettings.submit=Kullanıcıyı Kaydet
|
||||
adminUserSettings.changeUserRole=Kullanıcı rolünü değiştir
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -330,8 +335,8 @@ home.scalePages.title=Sayfa boyutunu/ölçeğini ayarla
|
|||
home.scalePages.desc=Bir sayfanın ve/veya içeriğinin boyutunu/ölçeğini değiştirir
|
||||
scalePages.tags=boyutlandır,değiştir,boyut,uyarla
|
||||
|
||||
home.pipeline.title=Hattı (İleri Seviye)
|
||||
home.pipeline.desc=Hattı betikleri tanımlayarak PDF'lere birden fazla işlemi çalıştır
|
||||
home.pipeline.title=Çoklu İşlemler (İleri Seviye)
|
||||
home.pipeline.desc=Çoklu İşlemler tanımlayarak PDF'lere birden fazla işlemi çalıştır
|
||||
pipeline.tags=otomatikleştir,sıralı,betikli,toplu-işlem
|
||||
|
||||
home.add-page-numbers.title=Sayfa Numaraları Ekle
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=Beni hatırla
|
|||
login.invalid=Geçersiz kullanıcı adı veya şifre.
|
||||
login.locked=Hesabınız kilitlendi.
|
||||
login.signinTitle=Lütfen giriş yapınız.
|
||||
login.ssoSignIn=Tek Oturum Açma ile Giriş Yap
|
||||
login.oauth2AutoCreateDisabled=OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -600,7 +607,7 @@ autoSplitPDF.submit=Gönder
|
|||
|
||||
|
||||
#pipeline
|
||||
pipeline.title=Pipeline
|
||||
pipeline.title=Çoklu İşlemler
|
||||
|
||||
|
||||
#pageLayout
|
||||
|
@ -669,7 +676,7 @@ BookToPDF.submit=Dönüştür
|
|||
#PDFToBook
|
||||
PDFToBook.title=PDF'den Kitaba
|
||||
PDFToBook.header=PDF'den Kitaba
|
||||
PDFToBook.selectText.1=Format
|
||||
PDFToBook.selectText.1=Format biçimi
|
||||
PDFToBook.credit=Kalibre Kullanır
|
||||
PDFToBook.submit=Dönüştür
|
||||
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF'den PDF/A'ya
|
|||
pdfToPDFA.credit=Bu hizmet PDF/A dönüşümü için OCRmyPDF kullanır
|
||||
pdfToPDFA.submit=Dönüştür
|
||||
pdfToPDFA.tip=Şu anda aynı anda birden fazla giriş için çalışmıyor
|
||||
pdfToPDFA.outputFormat=Çıkış formatı
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=Dikey bölme sayısını girin
|
|||
split-by-sections.submit=PDF'yi Böl
|
||||
split-by-sections.merge=Bir PDF'de Birleştirin
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Dosya Yazdır
|
||||
printFile.header=Dosyayı Yazıcıya Yazdır
|
||||
printFile.selectText.1=Yazdırılacak Dosyayı Seçin
|
||||
printFile.selectText.2=Yazıcı Adını Girin
|
||||
printFile.submit=Yazdır
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=Lisanslar
|
||||
licenses.title=3. Taraf Lisansları
|
||||
|
|
1063
src/main/resources/messages_uk_UA.properties
Normal file
1063
src/main/resources/messages_uk_UA.properties
Normal file
File diff suppressed because it is too large
Load diff
|
@ -57,6 +57,8 @@ usernameExistsMessage=新用户名已存在。
|
|||
invalidUsernameMessage=用户名无效,用户名只能由字母字符和数字组成。
|
||||
deleteCurrentUserMessage=无法删除当前登录的用户。
|
||||
deleteUsernameExistsMessage=用户名不存在,无法删除。
|
||||
downgradeCurrentUserMessage=无法降级当前用户的角色
|
||||
downgradeCurrentUserLongMessage=无法降级当前用户的角色。因此,当前用户将不会显示。
|
||||
error=错误
|
||||
oops=哎呀!
|
||||
help=帮助
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=设置
|
||||
settings.update=可更新
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=应用程序版本:
|
||||
settings.downloadOption.title=选择下载选项(单个文件非压缩文件):
|
||||
settings.downloadOption.1=在同一窗口打开
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=下载文件
|
|||
settings.zipThreshold=当下载的文件数量超过限制时,将文件压缩。
|
||||
settings.signOut=登出
|
||||
settings.accountSettings=帐号设定
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=更改凭证
|
||||
changeCreds.header=更新您的账户详情
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=演示用户(无自定义设置)
|
|||
adminUserSettings.internalApiUser=内部API用户
|
||||
adminUserSettings.forceChange=强制用户在登录时更改用户名/密码
|
||||
adminUserSettings.submit=保存用户
|
||||
adminUserSettings.changeUserRole=更改用户角色
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=记住我
|
|||
login.invalid=用户名或密码无效。
|
||||
login.locked=您的账户已被锁定。
|
||||
login.signinTitle=请登录
|
||||
login.ssoSignIn=通过单点登录登录
|
||||
login.oauth2AutoCreateDisabled=OAUTH2自动创建用户已禁用
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF转换为PDF/A
|
|||
pdfToPDFA.credit=此服务使用OCRmyPDF进行PDF/A转换
|
||||
pdfToPDFA.submit=转换
|
||||
pdfToPDFA.tip=目前不支持上传多个
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=输入垂直分割数
|
|||
split-by-sections.submit=分割PDF
|
||||
split-by-sections.merge=是否合并为一个pdf
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=许可证
|
||||
licenses.title=第三方许可证
|
||||
|
|
|
@ -57,17 +57,19 @@ usernameExistsMessage=新使用者名稱已存在。
|
|||
invalidUsernameMessage=使用者名無效,使用者名只能包含字母字元和數位。
|
||||
deleteCurrentUserMessage=無法刪除目前登錄的使用者。
|
||||
deleteUsernameExistsMessage=使用者名不存在,無法刪除。
|
||||
error=Error
|
||||
oops=Oops!
|
||||
help=Help
|
||||
goHomepage=Go to Homepage
|
||||
joinDiscord=Join our Discord server
|
||||
seeDockerHub=See Docker Hub
|
||||
visitGithub=Visit Github Repository
|
||||
donate=Donate
|
||||
color=Color
|
||||
sponsor=Sponsor
|
||||
info=Info
|
||||
downgradeCurrentUserMessage=無法降級目前使用者的角色
|
||||
downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,不會顯示目前的使用者。
|
||||
error=錯誤
|
||||
oops=哎呀!
|
||||
help=幫助
|
||||
goHomepage=前往首頁
|
||||
joinDiscord=加入我們的Discord服務器
|
||||
seeDockerHub=查看Docker Hub
|
||||
visitGithub=訪問Github存儲庫
|
||||
donate=捐贈
|
||||
color=顏色
|
||||
sponsor=贊助
|
||||
|
||||
|
||||
|
||||
|
@ -117,6 +119,7 @@ navbar.sections.edit=View & Edit
|
|||
#############
|
||||
settings.title=設定
|
||||
settings.update=有更新可用
|
||||
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
|
||||
settings.appVersion=應用版本:
|
||||
settings.downloadOption.title=選擇下載選項(對於單一檔案非壓縮下載):
|
||||
settings.downloadOption.1=在同一視窗中開啟
|
||||
|
@ -125,8 +128,9 @@ settings.downloadOption.3=下載檔案
|
|||
settings.zipThreshold=當下載的檔案數量超過時,壓縮檔案
|
||||
settings.signOut=登出
|
||||
settings.accountSettings=帳戶設定
|
||||
|
||||
|
||||
settings.bored.help=Enables easter egg game
|
||||
settings.cacheInputs.name=Save form inputs
|
||||
settings.cacheInputs.help=Enable to store previously used inputs for future runs
|
||||
|
||||
changeCreds.title=變更憑證
|
||||
changeCreds.header=更新您的帳戶詳細資訊
|
||||
|
@ -176,6 +180,7 @@ adminUserSettings.demoUser=示範用途的使用者(無自訂設定)
|
|||
adminUserSettings.internalApiUser=內部 API 使用者
|
||||
adminUserSettings.forceChange=強制使用者在登入時修改使用者名稱/密碼
|
||||
adminUserSettings.submit=儲存
|
||||
adminUserSettings.changeUserRole=更改使用者身份
|
||||
|
||||
#############
|
||||
# HOME-PAGE #
|
||||
|
@ -440,6 +445,8 @@ login.rememberme=記住我
|
|||
login.invalid=使用者名稱或密碼無效。
|
||||
login.locked=您的帳戶已被鎖定。
|
||||
login.signinTitle=請登入
|
||||
login.ssoSignIn=透過織網單一簽入
|
||||
login.oauth2AutoCreateDisabled=OAUTH2自動建立使用者已停用
|
||||
|
||||
|
||||
#auto-redact
|
||||
|
@ -945,6 +952,7 @@ pdfToPDFA.header=PDF 轉 PDF/A
|
|||
pdfToPDFA.credit=此服務使用 OCRmyPDF 進行 PDF/A 轉換
|
||||
pdfToPDFA.submit=轉換
|
||||
pdfToPDFA.tip=目前不支援上傳多個
|
||||
pdfToPDFA.outputFormat=Output format
|
||||
|
||||
|
||||
#PDFToWord
|
||||
|
@ -1028,6 +1036,15 @@ split-by-sections.vertical.placeholder=輸入垂直劃分的數量
|
|||
split-by-sections.submit=分割 PDF
|
||||
split-by-sections.merge=是否合併為一個pdf
|
||||
|
||||
|
||||
#printFile
|
||||
printFile.title=Print File
|
||||
printFile.header=Print File to Printer
|
||||
printFile.selectText.1=Select File to Print
|
||||
printFile.selectText.2=Enter Printer Name
|
||||
printFile.submit=Print
|
||||
|
||||
|
||||
#licenses
|
||||
licenses.nav=許可證
|
||||
licenses.title=第三方許可證
|
||||
|
@ -1038,15 +1055,15 @@ licenses.license=許可證
|
|||
|
||||
|
||||
# error
|
||||
error.sorry=Sorry for the issue!
|
||||
error.needHelp=Need help / Found an issue?
|
||||
error.contactTip=If you're still having trouble, don't hesitate to reach out to us for help. You can submit a ticket on our GitHub page or contact us through Discord:
|
||||
error.404.head=404 - Page Not Found | Oops, we tripped in the code!
|
||||
error.404.1=We can't seem to find the page you're looking for.
|
||||
error.404.2=Something went wrong
|
||||
error.github=Submit a ticket on GitHub
|
||||
error.showStack=Show Stack Trace
|
||||
error.copyStack=Copy Stack Trace
|
||||
error.githubSubmit=GitHub - Submit a ticket
|
||||
error.discordSubmit=Discord - Submit Support post
|
||||
error.sorry=對於這個問題,我們感到抱歉!
|
||||
error.needHelp=需要幫助/發現了一個問題?
|
||||
error.contactTip=如果你仍然遇到問題,請不要猶豫,隨時向我們尋求幫助。你可以在我們的GitHub頁面提交工單,或通過Discord與我們聯繋:
|
||||
error.404.head=404 - 找不到頁面 | 哎呀,我們在代碼中走錯了路!
|
||||
error.404.1=我們好像找不到你正在尋找的頁面。
|
||||
error.404.2=出了點錯誤
|
||||
error.github=在GitHub上提交工單
|
||||
error.showStack=顯示堆疊追蹤
|
||||
error.copyStack=複製堆疊追蹤
|
||||
error.githubSubmit=GitHub - 提交工單
|
||||
error.discordSubmit=Discord - 提交支援帖子
|
||||
|
||||
|
|
|
@ -7,16 +7,24 @@ security:
|
|||
csrfDisabled: true
|
||||
loginAttemptCount: 5 # lock user account after 5 tries
|
||||
loginResetTimeMinutes : 120 # lock account for 2 hours after x attempts
|
||||
#oauth2:
|
||||
# enabled: false # set to 'true' to enable login (Note: enableLogin must also be 'true' for this to work)
|
||||
# issuer: "" # set to any provider that supports OpenID Connect Discovery (/.well-known/openid-configuration) end-point
|
||||
# clientId: "" # Client ID from your provider
|
||||
# clientSecret: "" # Client Secret from your provider
|
||||
# autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
||||
|
||||
system:
|
||||
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc)
|
||||
googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
|
||||
enableAlphaFunctionality: false # Set to enable functionality which might need more testing before it fully goes live (This feature might make no changes)
|
||||
showUpdate: true # see when a new update is available
|
||||
showUpdateOnlyAdmin: false # Only admins can see when a new update is available, depending on showUpdate it must be set to 'true'
|
||||
|
||||
#ui:
|
||||
# appName: exampleAppName # Application's visible name
|
||||
# homeDescription: I am a description # Short description or tagline shown on homepage.
|
||||
# appNameNavbar: navbarName # Name displayed on the navigation bar
|
||||
ui:
|
||||
appName: null # Application's visible name
|
||||
homeDescription: null # Short description or tagline shown on homepage.
|
||||
appNameNavbar: null # Name displayed on the navigation bar
|
||||
|
||||
endpoints:
|
||||
toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
|
||||
|
@ -24,3 +32,7 @@ endpoints:
|
|||
|
||||
metrics:
|
||||
enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable
|
||||
|
||||
# Automatically Generated Settings (Do Not Edit Directly)
|
||||
AutomaticallyGenerated:
|
||||
key: example
|
|
@ -81,6 +81,13 @@
|
|||
"moduleLicense": "GNU Lesser General Public License v3 (LGPL-v3)",
|
||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/lgpl-3.0.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.github.stephenc.jcip:jcip-annotations",
|
||||
"moduleUrl": "http://stephenc.github.com/jcip-annotations",
|
||||
"moduleVersion": "1.0-1",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.github.vladimir-bukhtoyarov:bucket4j-core",
|
||||
"moduleUrl": "http://github.com/vladimir-bukhtoyarov/bucket4j/bucket4j-core",
|
||||
|
@ -109,6 +116,34 @@
|
|||
"moduleLicense": "LGPL",
|
||||
"moduleLicenseUrl": "http://www.martiansoftware.com/jsap/license.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.nimbusds:content-type",
|
||||
"moduleUrl": "https://connect2id.com",
|
||||
"moduleVersion": "2.2",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.nimbusds:lang-tag",
|
||||
"moduleUrl": "https://connect2id.com/",
|
||||
"moduleVersion": "1.7",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.nimbusds:nimbus-jose-jwt",
|
||||
"moduleUrl": "https://connect2id.com",
|
||||
"moduleVersion": "9.24.4",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.nimbusds:oauth2-oidc-sdk",
|
||||
"moduleUrl": "https://bitbucket.org/connect2id/oauth-2.0-sdk-with-openid-connect-extensions",
|
||||
"moduleVersion": "9.43.3",
|
||||
"moduleLicense": "Apache License, version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
||||
},
|
||||
{
|
||||
"moduleName": "com.opencsv:opencsv",
|
||||
"moduleUrl": "http://opencsv.sf.net",
|
||||
|
@ -335,6 +370,20 @@
|
|||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "net.minidev:accessors-smart",
|
||||
"moduleUrl": "https://urielch.github.io/",
|
||||
"moduleVersion": "2.5.0",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "net.minidev:json-smart",
|
||||
"moduleUrl": "https://urielch.github.io/",
|
||||
"moduleVersion": "2.5.0",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.antlr:antlr4-runtime",
|
||||
"moduleUrl": "https://www.antlr.org/",
|
||||
|
@ -547,6 +596,13 @@
|
|||
"moduleLicense": "Public Domain, per Creative Commons CC0",
|
||||
"moduleLicenseUrl": "http://creativecommons.org/publicdomain/zero/1.0/"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.ow2.asm:asm",
|
||||
"moduleUrl": "http://asm.ow2.org",
|
||||
"moduleVersion": "9.3",
|
||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.slf4j:jul-to-slf4j",
|
||||
"moduleUrl": "http://www.slf4j.org",
|
||||
|
@ -663,6 +719,13 @@
|
|||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter-oauth2-client",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
"moduleVersion": "3.2.4",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.boot:spring-boot-starter-security",
|
||||
"moduleUrl": "https://spring.io/projects/spring-boot",
|
||||
|
@ -726,6 +789,27 @@
|
|||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.security:spring-security-oauth2-client",
|
||||
"moduleUrl": "https://spring.io/projects/spring-security",
|
||||
"moduleVersion": "6.2.3",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.security:spring-security-oauth2-core",
|
||||
"moduleUrl": "https://spring.io/projects/spring-security",
|
||||
"moduleVersion": "6.2.3",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.security:spring-security-oauth2-jose",
|
||||
"moduleUrl": "https://spring.io/projects/spring-security",
|
||||
"moduleVersion": "6.2.3",
|
||||
"moduleLicense": "Apache License, Version 2.0",
|
||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0"
|
||||
},
|
||||
{
|
||||
"moduleName": "org.springframework.security:spring-security-web",
|
||||
"moduleUrl": "https://spring.io/projects/spring-security",
|
||||
|
|
|
@ -87,4 +87,43 @@
|
|||
.jumbotron {
|
||||
padding: 3rem 3rem;
|
||||
/* Reduce vertical padding */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
.lookatme {
|
||||
opacity: 1;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.lookatme::after {
|
||||
color: #e33100;
|
||||
text-shadow: 0 0 5px #e33100;
|
||||
/* in the html, the data-lookatme-text attribute must */
|
||||
/* contain the same text as the .lookatme element */
|
||||
content: attr(data-lookatme-text);
|
||||
padding: inherit;
|
||||
position: absolute;
|
||||
inset: 0 0 0 0;
|
||||
z-index: 1;
|
||||
/* 20 steps / 2 seconds = 10fps */
|
||||
-webkit-animation: 2s infinite Pulse steps(20);
|
||||
animation: 2s infinite Pulse steps(20);
|
||||
}
|
||||
|
||||
@keyframes Pulse {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
6
src/main/resources/static/images/flags/ua.svg
Normal file
6
src/main/resources/static/images/flags/ua.svg
Normal file
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" id="flag-icons-ua" viewBox="0 0 640 480">
|
||||
<g fill-rule="evenodd" stroke-width="1pt">
|
||||
<path fill="gold" d="M0 0h640v480H0z"/>
|
||||
<path fill="#0057b8" d="M0 0h640v240H0z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 232 B |
1
src/main/resources/static/images/update.svg
Normal file
1
src/main/resources/static/images/update.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M21,10.12H14.22L16.96,7.3C14.23,4.6 9.81,4.5 7.08,7.2C4.35,9.91 4.35,14.28 7.08,17C9.81,19.7 14.23,19.7 16.96,17C18.32,15.65 19,14.08 19,12.1H21C21,14.08 20.12,16.65 18.36,18.39C14.85,21.87 9.15,21.87 5.64,18.39C2.14,14.92 2.11,9.28 5.62,5.81C9.13,2.34 14.76,2.34 18.27,5.81L21,3V10.12M12.5,8V12.25L16,14.33L15.28,15.54L11,13V8H12.5Z" /></svg>
|
After Width: | Height: | Size: 412 B |
82
src/main/resources/static/js/cacheFormInputs.js
Normal file
82
src/main/resources/static/js/cacheFormInputs.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
document.addEventListener("DOMContentLoaded", function() {
|
||||
|
||||
var cacheInputs = localStorage.getItem("cacheInputs") || "disabled";
|
||||
if (cacheInputs !== "enabled") {
|
||||
return; // Stop execution if caching is not enabled
|
||||
}
|
||||
|
||||
// Function to generate a key based on the form's action attribute
|
||||
function generateStorageKey(form) {
|
||||
const action = form.getAttribute('action');
|
||||
if (!action || action.length < 3) {
|
||||
return null; // Not a valid action, return null to skip processing
|
||||
}
|
||||
return 'formData_' + encodeURIComponent(action);
|
||||
}
|
||||
|
||||
// Function to save form data to localStorage
|
||||
function saveFormData(form) {
|
||||
const formKey = generateStorageKey(form);
|
||||
if (!formKey) return; // Skip if no valid key
|
||||
|
||||
const formData = {};
|
||||
const elements = form.elements;
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
// Skip elements without names, passwords, files, hidden fields, and submit/reset buttons
|
||||
if (!element.name ||
|
||||
element.type === 'password' ||
|
||||
element.type === 'file' ||
|
||||
//element.type === 'hidden' ||
|
||||
element.type === 'submit' ||
|
||||
element.type === 'reset') {
|
||||
continue;
|
||||
}
|
||||
// Handle checkboxes: store only if checked
|
||||
if (element.type === 'checkbox') {
|
||||
if (element.checked) {
|
||||
formData[element.name] = element.value;
|
||||
} else {
|
||||
continue; // Skip unchecked boxes
|
||||
}
|
||||
} else {
|
||||
// Skip saving empty values
|
||||
if (element.value === "" || element.value == null) {
|
||||
continue;
|
||||
}
|
||||
formData[element.name] = element.value;
|
||||
}
|
||||
}
|
||||
localStorage.setItem(formKey, JSON.stringify(formData));
|
||||
}
|
||||
|
||||
// Function to load form data from localStorage
|
||||
function loadFormData(form) {
|
||||
const formKey = generateStorageKey(form);
|
||||
if (!formKey) return; // Skip if no valid key
|
||||
|
||||
const savedData = localStorage.getItem(formKey);
|
||||
if (savedData) {
|
||||
const formData = JSON.parse(savedData);
|
||||
for (const key in formData) {
|
||||
if (formData.hasOwnProperty(key) && form.elements[key]) {
|
||||
const element = form.elements[key];
|
||||
if (element.type === 'checkbox') {
|
||||
element.checked = true;
|
||||
} else {
|
||||
element.value = formData[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Attach event listeners and load data for all forms
|
||||
const forms = document.querySelectorAll('form');
|
||||
forms.forEach(form => {
|
||||
form.addEventListener('submit', function(event) {
|
||||
saveFormData(form);
|
||||
});
|
||||
loadFormData(form);
|
||||
});
|
||||
});
|
|
@ -5,74 +5,130 @@ const DraggableUtils = {
|
|||
pdfDoc: null,
|
||||
pageIndex: 0,
|
||||
documentsMap: new Map(),
|
||||
lastInteracted: null,
|
||||
|
||||
init() {
|
||||
interact(".draggable-canvas")
|
||||
.draggable({
|
||||
listeners: {
|
||||
move: (event) => {
|
||||
const target = event.target;
|
||||
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0) + event.dx;
|
||||
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0) + event.dy;
|
||||
.draggable({
|
||||
listeners: {
|
||||
move: (event) => {
|
||||
const target = event.target;
|
||||
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
|
||||
+ event.dx;
|
||||
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
|
||||
+ event.dy;
|
||||
|
||||
target.style.transform = `translate(${x}px, ${y}px)`;
|
||||
target.setAttribute("data-bs-x", x);
|
||||
target.setAttribute("data-bs-y", y);
|
||||
target.style.transform = `translate(${x}px, ${y}px)`;
|
||||
target.setAttribute("data-bs-x", x);
|
||||
target.setAttribute("data-bs-y", y);
|
||||
|
||||
this.onInteraction(target);
|
||||
},
|
||||
this.onInteraction(target);
|
||||
//update the last interacted element
|
||||
this.lastInteracted = event.target;
|
||||
},
|
||||
})
|
||||
.resizable({
|
||||
edges: { left: true, right: true, bottom: true, top: true },
|
||||
listeners: {
|
||||
move: (event) => {
|
||||
var target = event.target;
|
||||
var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
|
||||
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
|
||||
},
|
||||
})
|
||||
.resizable({
|
||||
edges: { left: true, right: true, bottom: true, top: true },
|
||||
listeners: {
|
||||
move: (event) => {
|
||||
var target = event.target;
|
||||
var x = parseFloat(target.getAttribute("data-bs-x")) || 0;
|
||||
var y = parseFloat(target.getAttribute("data-bs-y")) || 0;
|
||||
|
||||
// check if control key is pressed
|
||||
if (event.ctrlKey) {
|
||||
const aspectRatio = target.offsetWidth / target.offsetHeight;
|
||||
// preserve aspect ratio
|
||||
let width = event.rect.width;
|
||||
let height = event.rect.height;
|
||||
// check if control key is pressed
|
||||
if (event.ctrlKey) {
|
||||
const aspectRatio = target.offsetWidth / target.offsetHeight;
|
||||
// preserve aspect ratio
|
||||
let width = event.rect.width;
|
||||
let height = event.rect.height;
|
||||
|
||||
if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) {
|
||||
height = width / aspectRatio;
|
||||
} else {
|
||||
width = height * aspectRatio;
|
||||
}
|
||||
|
||||
event.rect.width = width;
|
||||
event.rect.height = height;
|
||||
if (Math.abs(event.deltaRect.width) >= Math.abs(
|
||||
event.deltaRect.height)) {
|
||||
height = width / aspectRatio;
|
||||
} else {
|
||||
width = height * aspectRatio;
|
||||
}
|
||||
|
||||
target.style.width = event.rect.width + "px";
|
||||
target.style.height = event.rect.height + "px";
|
||||
event.rect.width = width;
|
||||
event.rect.height = height;
|
||||
}
|
||||
|
||||
// translate when resizing from top or left edges
|
||||
x += event.deltaRect.left;
|
||||
y += event.deltaRect.top;
|
||||
target.style.width = event.rect.width + "px";
|
||||
target.style.height = event.rect.height + "px";
|
||||
|
||||
target.style.transform = "translate(" + x + "px," + y + "px)";
|
||||
// translate when resizing from top or left edges
|
||||
x += event.deltaRect.left;
|
||||
y += event.deltaRect.top;
|
||||
|
||||
target.setAttribute("data-bs-x", x);
|
||||
target.setAttribute("data-bs-y", y);
|
||||
target.textContent = Math.round(event.rect.width) + "\u00D7" + Math.round(event.rect.height);
|
||||
target.style.transform = "translate(" + x + "px," + y + "px)";
|
||||
|
||||
this.onInteraction(target);
|
||||
},
|
||||
target.setAttribute("data-bs-x", x);
|
||||
target.setAttribute("data-bs-y", y);
|
||||
target.textContent = Math.round(event.rect.width) + "\u00D7"
|
||||
+ Math.round(event.rect.height);
|
||||
|
||||
this.onInteraction(target);
|
||||
},
|
||||
},
|
||||
|
||||
modifiers: [
|
||||
interact.modifiers.restrictSize({
|
||||
min: { width: 5, height: 5 },
|
||||
}),
|
||||
],
|
||||
inertia: true,
|
||||
modifiers: [
|
||||
interact.modifiers.restrictSize({
|
||||
min: {width: 5, height: 5},
|
||||
}),
|
||||
],
|
||||
inertia: true,
|
||||
});
|
||||
//Arrow key Support for Add-Image and Sign pages
|
||||
if(window.location.pathname.endsWith('sign') || window.location.pathname.endsWith('add-image')) {
|
||||
window.addEventListener('keydown', (event) => {
|
||||
//Check for last interacted element
|
||||
if (!this.lastInteracted){
|
||||
return;
|
||||
}
|
||||
// Get the currently selected element
|
||||
const target = this.lastInteracted;
|
||||
|
||||
// Step size relatively to the elements size
|
||||
const stepX = target.offsetWidth * 0.05;
|
||||
const stepY = target.offsetHeight * 0.05;
|
||||
|
||||
// Get the current x and y coordinates
|
||||
let x = (parseFloat(target.getAttribute('data-bs-x')) || 0);
|
||||
let y = (parseFloat(target.getAttribute('data-bs-y')) || 0);
|
||||
|
||||
// Check which key was pressed and update the coordinates accordingly
|
||||
switch (event.key) {
|
||||
case 'ArrowUp':
|
||||
y -= stepY;
|
||||
event.preventDefault(); // Prevent the default action
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
y += stepY;
|
||||
event.preventDefault();
|
||||
break;
|
||||
case 'ArrowLeft':
|
||||
x -= stepX;
|
||||
event.preventDefault();
|
||||
break;
|
||||
case 'ArrowRight':
|
||||
x += stepX;
|
||||
event.preventDefault();
|
||||
break;
|
||||
default:
|
||||
return; // Listen only to arrow keys
|
||||
}
|
||||
|
||||
// Update position
|
||||
target.style.transform = `translate(${x}px, ${y}px)`;
|
||||
target.setAttribute('data-bs-x', x);
|
||||
target.setAttribute('data-bs-y', y);
|
||||
|
||||
DraggableUtils.onInteraction(target);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
onInteraction(target) {
|
||||
this.boxDragContainer.appendChild(target);
|
||||
},
|
||||
|
@ -88,9 +144,18 @@ const DraggableUtils = {
|
|||
createdCanvas.setAttribute("data-bs-x", x);
|
||||
createdCanvas.setAttribute("data-bs-y", y);
|
||||
|
||||
//Click element in order to enable arrow keys
|
||||
createdCanvas.addEventListener('click', () => {
|
||||
this.lastInteracted = createdCanvas;
|
||||
});
|
||||
|
||||
createdCanvas.onclick = (e) => this.onInteraction(e.target);
|
||||
|
||||
this.boxDragContainer.appendChild(createdCanvas);
|
||||
|
||||
//Enable Arrow keys directly after the element is created
|
||||
this.lastInteracted = createdCanvas;
|
||||
|
||||
return createdCanvas;
|
||||
},
|
||||
createDraggableCanvasFromUrl(dataUrl) {
|
||||
|
@ -134,6 +199,11 @@ const DraggableUtils = {
|
|||
},
|
||||
deleteDraggableCanvas(element) {
|
||||
if (element) {
|
||||
//Check if deleted element is the last interacted
|
||||
if (this.lastInteracted === element) {
|
||||
// If it is, set lastInteracted to null
|
||||
this.lastInteracted = null;
|
||||
}
|
||||
element.remove();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -104,4 +104,9 @@ function setupFileInput(chooser) {
|
|||
$(inputElement).siblings(".custom-file-label").addClass("selected").html(pdfPrompt);
|
||||
}
|
||||
}
|
||||
//Listen for event of file being removed and the filter it out of the allFiles array
|
||||
document.addEventListener("fileRemoved", function (e) {
|
||||
const fileName = e.detail;
|
||||
allFiles = allFiles.filter(file => file.name !== fileName);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -30,19 +30,39 @@ async function getLatestReleaseVersion() {
|
|||
|
||||
async function checkForUpdate() {
|
||||
// Initialize the update button as hidden
|
||||
var updateBtn = document.getElementById("update-btn");
|
||||
var updateBtn = document.getElementById("update-btn") || null;
|
||||
var updateLink = document.getElementById("update-link") || null;
|
||||
if (updateBtn !== null) {
|
||||
updateBtn.style.display = "none";
|
||||
}
|
||||
if (updateLink !== null) {
|
||||
console.log("hidden!");
|
||||
if (!updateLink.classList.contains("visually-hidden")) {
|
||||
updateLink.classList.add("visually-hidden");
|
||||
}
|
||||
}
|
||||
|
||||
const latestVersion = await getLatestReleaseVersion();
|
||||
console.log("latestVersion=" + latestVersion);
|
||||
console.log("currentVersion=" + currentVersion);
|
||||
console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion));
|
||||
if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
|
||||
document.getElementById("update-btn").style.display = "block";
|
||||
if (updateBtn != null) {
|
||||
document.getElementById("update-btn").style.display = "block";
|
||||
}
|
||||
if (updateLink !== null) {
|
||||
document.getElementById("app-update").innerHTML = updateAvailable.replace("{0}", '<b>' + currentVersion + '</b>').replace("{1}", '<b>' + latestVersion + '</b>');
|
||||
if (updateLink.classList.contains("visually-hidden")) {
|
||||
updateLink.classList.remove("visually-hidden");
|
||||
}
|
||||
}
|
||||
console.log("visible");
|
||||
} else {
|
||||
if (updateLink !== null) {
|
||||
if (!updateLink.classList.contains("visually-hidden")) {
|
||||
updateLink.classList.add("visually-hidden");
|
||||
}
|
||||
}
|
||||
console.log("hidden");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,12 @@ function reorderCards() {
|
|||
cards.sort(function (a, b) {
|
||||
var aIsFavorite = localStorage.getItem(a.id) === "favorite";
|
||||
var bIsFavorite = localStorage.getItem(b.id) === "favorite";
|
||||
if (a.id === "update-link") {
|
||||
return -1;
|
||||
}
|
||||
if (b.id === "update-link") {
|
||||
return 1;
|
||||
}
|
||||
if (aIsFavorite && !bIsFavorite) {
|
||||
return -1;
|
||||
}
|
||||
|
|
|
@ -69,8 +69,13 @@ function attachMoveButtons() {
|
|||
removeButtons[i].addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
var parent = this.closest(".list-group-item");
|
||||
//Get name of removed file
|
||||
var fileName = parent.querySelector(".filename").innerText;
|
||||
parent.remove();
|
||||
updateFiles();
|
||||
//Dispatch a custom event with the name of the removed file
|
||||
var event = new CustomEvent("fileRemoved", { detail: fileName });
|
||||
document.dispatchEvent(event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,3 +31,12 @@ document.getElementById("boredWaiting").addEventListener("change", function () {
|
|||
boredWaiting = this.checked ? "enabled" : "disabled";
|
||||
localStorage.setItem("boredWaiting", boredWaiting);
|
||||
});
|
||||
|
||||
var cacheInputs = localStorage.getItem("cacheInputs") || "disabled";
|
||||
document.getElementById("cacheInputs").checked = cacheInputs === "enabled";
|
||||
|
||||
document.getElementById("cacheInputs").addEventListener("change", function () {
|
||||
cacheInputs = this.checked ? "enabled" : "disabled";
|
||||
localStorage.setItem("cacheInputs", cacheInputs);
|
||||
});
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@
|
|||
</th:block>
|
||||
<!-- Change Username Form -->
|
||||
<h4 th:text="#{account.changeUsername}">Change Username?</h4>
|
||||
<form id="bg-card" class="mt-4 mb-4" action="api/v1/user/change-username" method="post">
|
||||
<form th:if="${!oAuth2Login}" id="bg-card" class="mt-4 mb-4" action="api/v1/user/change-username" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="newUsername" th:text="#{account.newUsername}">Change Username</label>
|
||||
<input type="text" class="form-control" name="newUsername" id="newUsername"
|
||||
|
@ -62,7 +62,7 @@
|
|||
</form>
|
||||
|
||||
<!-- Change Password Form -->
|
||||
<h4 th:text="#{account.changePassword}">Change Password?</h4>
|
||||
<h4 th:if="${!oAuth2Login}" th:text="#{account.changePassword}">Change Password?</h4>
|
||||
<form id="bg-card" class="mt-4 mb-4" action="api/v1/user/change-password" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
|
||||
|
@ -85,6 +85,7 @@
|
|||
</div>
|
||||
</form>
|
||||
|
||||
|
||||
<!-- API Key Form -->
|
||||
<h4 th:text="#{account.yourApiKey}">API Key</h4>
|
||||
<div class="card mt-4 mb-4">
|
||||
|
|
|
@ -58,6 +58,7 @@
|
|||
<div class="mb-3">
|
||||
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
||||
<select name="role" class="form-control" required>
|
||||
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
||||
<option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">Role</option>
|
||||
</select>
|
||||
</div>
|
||||
|
@ -66,6 +67,32 @@
|
|||
<label class="form-check-label" for="forceChange" th:text="#{adminUserSettings.forceChange}">Force user to change username/password on login</label>
|
||||
</div>
|
||||
|
||||
<!-- Add other fields as required -->
|
||||
<button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
|
||||
</form>
|
||||
<hr />
|
||||
<h2 th:text="#{adminUserSettings.changeUserRole}">Change User's Role</h2>
|
||||
<button class="btn btn-outline-info" data-toggle="tooltip" data-placement="auto" th:title="#{downgradeCurrentUserLongMessage}" th:text="#{help}">Help</button>
|
||||
<div th:if="${param.messageType != null and param.messageType.size() > 0 and (param.messageType[0] == 'userNotFound' or param.messageType[0] == 'downgradeCurrentUser')}" class="alert alert-danger">
|
||||
<span th:if="${param.messageType[0] == 'userNotFound'}" th:text="#{userNotFoundMessage}">Username not found</span>
|
||||
<span th:if="${param.messageType[0] == 'downgradeCurrentUser'}" th:text="#{downgradeCurrentUserMessage}">Cannot downgrade current user's role</span>
|
||||
</div>
|
||||
<form action="/api/v1/user/admin/changeRole" method="post">
|
||||
<div class="mb-3">
|
||||
<label for="username" th:text="#{username}">Username</label>
|
||||
<select name="username" class="form-control" required>
|
||||
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
||||
<option th:each="user : ${users}" th:if="${user.username != currentUsername}" th:value="${user.username}" th:text="${user.username}">Username</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="role" th:text="#{adminUserSettings.role}">Role</label>
|
||||
<select name="role" class="form-control" required>
|
||||
<option value="" disabled selected th:text="#{selectFillter}">-- Select --</option>
|
||||
<option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">Role</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Add other fields as required -->
|
||||
<button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
|
||||
</form>
|
||||
|
@ -73,6 +100,11 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script th:inline="javascript">
|
||||
$(document).ready(function() {
|
||||
$('[data-toggle="tooltip"]').tooltip()
|
||||
})
|
||||
</script>
|
||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
@ -20,6 +20,13 @@
|
|||
<p th:text="#{pdfToPDFA.tip}"></p>
|
||||
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/pdfa}">
|
||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
|
||||
<div class="mb-3">
|
||||
<label th:text="#{pdfToPDFA.outputFormat}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="pdfa-1">PDF/A-1b</option>
|
||||
<option value="pdfa">PDF/A-2b</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToPDFA.submit}"></button>
|
||||
</form>
|
||||
|
@ -31,4 +38,4 @@
|
|||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
@ -22,14 +22,13 @@
|
|||
<div class="mb-3">
|
||||
<label th:text="#{PDFToText.selectText.1}"></label>
|
||||
<select class="form-control" name="outputFormat">
|
||||
<option value="rtf">RTF</option>
|
||||
<option th:if="${@endpointConfiguration.isEndpointEnabled('pdf-to-rtf')}" value="rtf">RTF</option>
|
||||
<option value="txt">TXT</option>
|
||||
</select>
|
||||
</div>
|
||||
<br>
|
||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
|
||||
</form>
|
||||
<p class="mt-3" th:text="#{PDFToText.credit}"></p>
|
||||
<p th:if="${@endpointConfiguration.isEndpointEnabled('pdf-to-rtf')}" class="mt-3" th:text="#{PDFToText.credit}"></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -66,6 +66,7 @@
|
|||
<!-- Help Modal -->
|
||||
<link rel="stylesheet" href="css/errorBanner.css">
|
||||
|
||||
<script src="js/cacheFormInputs.js"></script>
|
||||
<script src="js/tab-container.js"></script>
|
||||
<script src="js/darkmode.js"></script>
|
||||
</th:block>
|
||||
|
|
|
@ -26,4 +26,5 @@
|
|||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hu_HU"> <img src="images/flags/hu.svg" alt="icon" width="20" height="15"> Hungarian</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="hi_IN"> <img src="images/flags/in.svg" alt="icon" width="20" height="15"> हिन्दी</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="sr_LATN_RS"> <img src="images/flags/rs.svg" alt="icon" width="20" height="15"> Srpski</a>
|
||||
<a class="dropdown-item lang_dropdown-item" href="" data-bs-language-code="uk_UA"> <img src="images/flags/ua.svg" alt="icon" width="20" height="15"> Українська</a>
|
||||
</th:block>
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
<script th:inline="javascript">
|
||||
const currentVersion = /*[[${@appVersion}]]*/ '';
|
||||
const noFavourites = /*[[#{noFavourites}]]*/ '';
|
||||
const updateAvailable = /*[[#{settings.updateAvailable}]]*/ '';
|
||||
</script>
|
||||
<script th:src="@{js/githubVersion.js}"></script>
|
||||
<nav class="navbar navbar-expand-lg">
|
||||
|
@ -367,7 +368,7 @@
|
|||
|
||||
<a href="swagger-ui/index.html" class="btn btn-sm btn-outline-primary mx-1" role="button"
|
||||
target="_blank">API</a>
|
||||
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases"
|
||||
<a th:if="${@shouldShow}" href="https://github.com/Stirling-Tools/Stirling-PDF/releases"
|
||||
class="btn btn-sm btn-outline-primary mx-1" id="update-btn" th:utext="#{settings.update}" role="button"
|
||||
target="_blank"></a>
|
||||
</div>
|
||||
|
@ -389,14 +390,19 @@
|
|||
<span id="zipThresholdValue" class="ms-2"></span>
|
||||
</div>
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" id="boredWaiting">
|
||||
<input type="checkbox" id="boredWaiting" th:title="#{settings.bored.help}">
|
||||
<label for="boredWaiting" th:text="#{bored}"></label>
|
||||
</div>
|
||||
<a th:if="${@loginEnabled}" href="account" class="btn btn-sm btn-outline-primary" role="button"
|
||||
<div class="form-check mb-3">
|
||||
<input type="checkbox" id="cacheInputs" th:title="#{settings.cacheInputs.help}">
|
||||
<label for="cacheInputs" th:text="#{settings.cacheInputs.name}"></label>
|
||||
</div>
|
||||
|
||||
<a th:if="${@loginEnabled and @activSecurity}" href="account" class="btn btn-sm btn-outline-primary" role="button"
|
||||
th:text="#{settings.accountSettings}" target="_blank">Account Settings</a>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<a th:if="${@loginEnabled}" class="btn btn-danger" role="button" th:text="#{settings.signOut}"
|
||||
<a th:if="${@loginEnabled and @activSecurity}" class="btn btn-danger" role="button" th:text="#{settings.signOut}"
|
||||
href="logout">Sign Out</a>
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
|
||||
</div>
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue