Merge branch 'Stirling-Tools:main' into fixFrontEndIssue

This commit is contained in:
Dimitris Doukas 2024-05-12 13:55:51 +03:00 committed by GitHub
commit 21e5002d73
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
103 changed files with 3445 additions and 1131 deletions

View File

@ -26,15 +26,17 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: "17"
distribution: 'temurin' distribution: "temurin"
- uses: gradle/gradle-build-action@v2.4.2 - uses: gradle/actions/setup-gradle@v3
with: with:
gradle-version: 7.6 gradle-version: 7.6
arguments: build --no-build-cache
- name: Build with Gradle
run: ./gradlew build --no-build-cache

View File

@ -5,7 +5,7 @@ on:
branches: branches:
- main - main
paths: paths:
- 'build.gradle' - "build.gradle"
permissions: permissions:
contents: write contents: write
@ -17,13 +17,15 @@ jobs:
steps: steps:
- name: Check out code - name: Check out code
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: "17"
distribution: 'adopt' distribution: "adopt"
- uses: gradle/actions/setup-gradle@v3
- name: Run Gradle Command - name: Run Gradle Command
run: ./gradlew clean generateLicenseReport run: ./gradlew clean generateLicenseReport
@ -44,7 +46,7 @@ jobs:
- name: Create Pull Request - name: Create Pull Request
if: env.CHANGES_DETECTED == 'true' if: env.CHANGES_DETECTED == 'true'
uses: peter-evans/create-pull-request@v3 uses: peter-evans/create-pull-request@v6
with: with:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update 3rd Party Licenses" commit-message: "Update 3rd Party Licenses"
@ -58,4 +60,3 @@ jobs:
[1]: https://github.com/peter-evans/create-pull-request [1]: https://github.com/peter-evans/create-pull-request
draft: false draft: false
delete-branch: true delete-branch: true

View File

@ -14,50 +14,54 @@ jobs:
push: push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3.5.2
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3.11.0 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: "17"
distribution: 'temurin' distribution: "temurin"
- uses: gradle/actions/setup-gradle@v3
- uses: gradle/gradle-build-action@v2.4.2
env:
DOCKER_ENABLE_SECURITY: false
with: with:
gradle-version: 7.6 gradle-version: 7.6
arguments: clean build
- name: Make Gradle wrapper executable - name: Run Gradle Command
run: chmod +x gradlew run: ./gradlew clean build
env:
DOCKER_ENABLE_SECURITY: false
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
- name: Get version number - name: Get version number
id: versionNumber id: versionNumber
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Login to Docker Hub - name: Login to Docker Hub
uses: docker/login-action@v2.1.0 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKER_HUB_USERNAME }} username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }} password: ${{ secrets.DOCKER_HUB_API }}
- name: Login to GitHub Container Registry - name: Login to GitHub Container Registry
uses: docker/login-action@v2.1.0 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ github.token }} password: ${{ github.token }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Convert repository owner to lowercase - name: Convert repository owner to lowercase
id: repoowner id: repoowner
run: echo "::set-output name=lowercase::$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
- name: Generate tags - name: Generate tags
id: meta id: meta
uses: docker/metadata-action@v4.4.0 uses: docker/metadata-action@v5
with: with:
images: | images: |
${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf ${{ secrets.DOCKER_HUB_USERNAME }}/s-pdf
@ -67,31 +71,23 @@ jobs:
type=raw,value=latest,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' }} type=raw,value=alpha,enable=${{ github.ref == 'refs/heads/main' }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v2.1.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2.5.0
- name: Build and push main Dockerfile - name: Build and push main Dockerfile
uses: docker/build-push-action@v4.0.0 uses: docker/build-push-action@v5
with: with:
builder: ${{ steps.buildx.outputs.name }}
context: . context: .
dockerfile: ./Dockerfile file: ./Dockerfile
push: true push: true
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8 platforms: linux/amd64,linux/arm64/v8
- name: Generate tags ultra-lite - name: Generate tags ultra-lite
id: meta2 id: meta2
uses: docker/metadata-action@v4.4.0 uses: docker/metadata-action@v5
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
images: | images: |
@ -101,9 +97,8 @@ jobs:
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }} 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' }} type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' }}
- name: Build and push Dockerfile-ultra-lite - name: Build and push Dockerfile-ultra-lite
uses: docker/build-push-action@v4.0.0 uses: docker/build-push-action@v5
if: github.ref != 'refs/heads/main' if: github.ref != 'refs/heads/main'
with: with:
context: . context: .
@ -113,6 +108,5 @@ jobs:
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
tags: ${{ steps.meta2.outputs.tags }} tags: ${{ steps.meta2.outputs.tags }}
labels: ${{ steps.meta2.outputs.labels }} labels: ${{ steps.meta2.outputs.labels }}
build-args: build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64,linux/arm64/v8 platforms: linux/amd64,linux/arm64/v8

View File

@ -1,6 +1,7 @@
name: Release Artifacts name: Release Artifacts
on: on:
workflow_dispatch:
release: release:
types: [created] types: [created]
permissions: permissions:
@ -14,44 +15,61 @@ jobs:
enable_security: [true, false] enable_security: [true, false]
include: include:
- enable_security: true - enable_security: true
file_suffix: '-with-login' file_suffix: "-with-login"
- enable_security: false - enable_security: false
file_suffix: '' file_suffix: ""
steps: steps:
- uses: actions/checkout@v3.5.2 - uses: actions/checkout@v4
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3.11.0 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: "17"
distribution: 'temurin' distribution: "temurin"
- name: Grant execute permission for gradlew - uses: gradle/actions/setup-gradle@v3
run: chmod +x gradlew with:
gradle-version: 7.6
- name: Generate jar (With Security=${{ matrix.enable_security }}) - name: Generate jar (With Security=${{ matrix.enable_security }})
run: ./gradlew clean createExe run: ./gradlew clean createExe
env: env:
DOCKER_ENABLE_SECURITY: ${{ matrix.enable_security }} 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 - name: Get version number
id: versionNumber id: versionNumber
run: echo "::set-output name=versionNumber::$(./gradlew printVersion --quiet | tail -1)" run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
- name: Rename binarie
if: matrix.file_suffix != ''
run: cp ./build/launch4j/Stirling-PDF.exe ./build/launch4j/Stirling-PDF${{ matrix.file_suffix }}.exe
- 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 - name: Upload jar binaries to release
uses: svenstaro/upload-release-action@v2 uses: softprops/action-gh-release@v2
with: with:
repo_token: ${{ secrets.GITHUB_TOKEN }} files: ./build/libs/Stirling-PDF${{ matrix.file_suffix }}.jar
file: ./build/libs/Stirling-PDF-${{ steps.versionNumber.outputs.versionNumber }}.jar
asset_name: Stirling-PDF${{ matrix.file_suffix }}.jar
tag: ${{ github.ref }}
overwrite: true

View File

@ -8,20 +8,17 @@ on:
jobs: jobs:
push: push:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v3.5.2
- name: Set up JDK 17 - name: Set up JDK 17
uses: actions/setup-java@v3.11.0 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: "17"
distribution: 'temurin' distribution: "temurin"
- name: Grant execute permission for gradlew - uses: gradle/actions/setup-gradle@v3
run: chmod +x gradlew
- name: Generate Swagger documentation - name: Generate Swagger documentation
run: ./gradlew generateOpenApiDocs run: ./gradlew generateOpenApiDocs
@ -31,6 +28,10 @@ jobs:
env: env:
SWAGGERHUB_API_KEY: ${{ secrets.SWAGGERHUB_API_KEY }} 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 - name: Set API version as published and default on SwaggerHub
run: | 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}" 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}"

View File

@ -7,6 +7,7 @@ on:
paths: paths:
- "build.gradle" - "build.gradle"
- "src/main/resources/messages_*.properties" - "src/main/resources/messages_*.properties"
- "scripts/translation_status.toml"
permissions: permissions:
contents: write contents: write
@ -58,6 +59,8 @@ jobs:
uses: actions/setup-python@v5.1.0 uses: actions/setup-python@v5.1.0
with: with:
python-version: "3.x" python-version: "3.x"
- name: Install dependencies
run: pip install tomlkit
- name: Sync README - name: Sync README
run: python scripts/counter_translation.py run: python scripts/counter_translation.py
- name: Set up git config - name: Set up git config

View File

@ -3,12 +3,12 @@ name: Docker Compose Tests
on: on:
pull_request: pull_request:
paths: paths:
- 'src/**' - "src/**"
- '**.gradle' - "**.gradle"
- '!src/main/java/resources/messages*' - "!src/main/java/resources/messages*"
- 'exampleYmlFiles/**' - "exampleYmlFiles/**"
- 'Dockerfile' - "Dockerfile"
- 'Dockerfile**' - "Dockerfile**"
jobs: jobs:
test: test:
@ -16,39 +16,21 @@ jobs:
steps: steps:
- name: Checkout Repository - name: Checkout Repository
uses: actions/checkout@v2 uses: actions/checkout@v4
- name: Set up Java 17 - name: Set up Java 17
uses: actions/setup-java@v2 uses: actions/setup-java@v4
with: with:
java-version: '17' java-version: "17"
distribution: 'adopt' distribution: "adopt"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1 uses: docker/setup-buildx-action@v3
- name: Run Docker Compose Tests
run: |
chmod +x ./gradlew
- 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 - name: Install Docker Compose
run: | 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 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 # sudo chmod +x /usr/local/bin/docker-compose
- name: Run Docker Compose Tests - name: Run Docker Compose Tests
run: | run: |

View File

@ -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! 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 ## Fixing Bugs or Adding a New Feature
First, make sure you've read the section [Pull Requests](#pull-requests). First, make sure you've read the section [Pull Requests](#pull-requests).

View File

@ -4,8 +4,8 @@ FROM alpine:20240329
# Copy necessary files # Copy necessary files
COPY scripts /scripts COPY scripts /scripts
COPY pipeline /pipeline COPY pipeline /pipeline
COPY src/main/resources/static/fonts/*.ttf /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 src/main/resources/static/fonts/*.otf /usr/share/fonts/opentype/noto/
COPY build/libs/*.jar app.jar COPY build/libs/*.jar app.jar
ARG VERSION_TAG 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 && \ 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/community" | tee -a /etc/apk/repositories && \
echo "@testing https://dl-cdn.alpinelinux.org/alpine/edge/testing" | 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 \ apk add --no-cache \
ca-certificates \ ca-certificates \
tzdata \ tzdata \
tini \ tini \
openssl \
openssl-dev \
bash \ bash \
curl \ curl \
openjdk17-jre \ openjdk17-jre \
su-exec \ su-exec \
font-noto-cjk \
shadow \ shadow \
# Doc conversion # Doc conversion
libreoffice@testing \ 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 && \ addgroup -S stirlingpdfgroup && adduser -S stirlingpdfuser -G stirlingpdfgroup && \
chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \ chown -R stirlingpdfuser:stirlingpdfgroup $HOME /scripts /usr/share/fonts/opentype/noto /configs /customFiles /pipeline && \
chown stirlingpdfuser:stirlingpdfgroup /app.jar && \ chown stirlingpdfuser:stirlingpdfgroup /app.jar && \
tesseract --list-langs tesseract --list-langs && \
rm -rf /var/cache/apk/*
EXPOSE 8080 EXPOSE 8080

View File

@ -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) 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.

View File

@ -51,8 +51,16 @@ For Fedora-based systems use this command:
sudo dnf install -y git automake autoconf libtool leptonica-devel pkg-config zlib-devel make gcc-c++ java-17-openjdk python3 python3-pip sudo dnf install -y git automake autoconf libtool leptonica-devel pkg-config zlib-devel make gcc-c++ java-17-openjdk python3 python3-pip
``` ```
For non-root users with Nix Package Manager, use the following command:
```bash
nix-channel --update
nix-env -iA nixpkgs.jdk17 nixpkgs.git nixpkgs.python38 nixpkgs.gnumake nixpkgs.libgcc nixpkgs.automake nixpkgs.autoconf nixpkgs.libtool nixpkgs.pkg-config nixpkgs.zlib nixpkgs.leptonica
```
### Step 2: Clone and Build jbig2enc (Only required for certain OCR functionality) ### Step 2: Clone and Build jbig2enc (Only required for certain OCR functionality)
For Debian and Fedora, you can build it from source using the following commands:
```bash ```bash
mkdir ~/.git mkdir ~/.git
cd ~/.git &&\ cd ~/.git &&\
@ -64,6 +72,11 @@ make &&\
sudo make install sudo make install
``` ```
For Nix, you will face `Leptonica not detected`. Bypass this by installing it directly using the following command:
```bash
nix-env -iA nixpkgs.jbig2enc
```
### Step 3: Install Additional Software ### Step 3: Install Additional Software
Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for pattern recognition functionality. Next we need to install LibreOffice for conversions, ocrmypdf for OCR, and opencv for pattern recognition functionality.
@ -105,6 +118,13 @@ sudo dnf install -y libreoffice-writer libreoffice-calc libreoffice-impress unpa
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
``` ```
For Nix:
```bash
nix-env -iA nixpkgs.unpaper nixpkgs.libreoffice nixpkgs.ocrmypdf nixpkgs.poppler_utils
pip3 install uno opencv-python-headless unoconv pngquant WeasyPrint
```
### Step 4: Clone and Build Stirling-PDF ### Step 4: Clone and Build Stirling-PDF
```bash ```bash
@ -115,13 +135,12 @@ chmod +x ./gradlew &&\
./gradlew build ./gradlew build
``` ```
### Step 5: Move jar to desired location ### Step 5: Move jar to desired location
After the build process, a `.jar` file will be generated in the `build/libs` directory. After the build process, a `.jar` file will be generated in the `build/libs` directory.
You can move this file to a desired location, for example, `/opt/Stirling-PDF/`. You can move this file to a desired location, for example, `/opt/Stirling-PDF/`.
You must also move the Script folder within the Stirling-PDF repo that you have downloaded to this directory. You must also move the Script folder within the Stirling-PDF repo that you have downloaded to this directory.
This folder is required for the python scripts using OpenCV This folder is required for the python scripts using OpenCV.
```bash ```bash
sudo mkdir /opt/Stirling-PDF &&\ sudo mkdir /opt/Stirling-PDF &&\
@ -129,19 +148,25 @@ sudo mv ./build/libs/Stirling-PDF-*.jar /opt/Stirling-PDF/ &&\
sudo mv scripts /opt/Stirling-PDF/ &&\ sudo mv scripts /opt/Stirling-PDF/ &&\
echo "Scripts installed." echo "Scripts installed."
``` ```
For non-root users, you can just keep the jar in the main directory of Stirling-PDF using the following command:
```bash
mv ./build/libs/Stirling-PDF-*.jar ./Stirling-PDF-*.jar
```
### Step 6: Other files ### Step 6: Other files
#### OCR #### OCR
If you plan to use the OCR (Optical Character Recognition) functionality, you might need to install language packs for Tesseract if running non-english scanning. If you plan to use the OCR (Optical Character Recognition) functionality, you might need to install language packs for Tesseract if running non-english scanning.
##### Installing Language Packs ##### Installing Language Packs
Easiest is to use the langpacks provided by your repositories. Skip the other steps Easiest is to use the langpacks provided by your repositories. Skip the other steps.
Manual: Manual:
1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need. 1. Download the desired language pack(s) by selecting the `.traineddata` file(s) for the language(s) you need.
2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata` 2. Place the `.traineddata` files in the Tesseract tessdata directory: `/usr/share/tessdata`
3. 3. Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info.
Please view [OCRmyPDF install guide](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for more info.
**IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED. **IMPORTANT:** DO NOT REMOVE EXISTING `eng.traineddata`, IT'S REQUIRED.
Debian based systems, install languages with this command: Debian based systems, install languages with this command:
@ -171,14 +196,38 @@ dnf search -C tesseract-langpack-
rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g' rpm -qa | grep tesseract-langpack | sed 's/tesseract-langpack-//g'
``` ```
Nix:
```bash
nix-env -iA nixpkgs.tesseract
```
**Note:** Nix Package Manager pre-installs almost all the language packs when tesseract is installed.
### Step 7: Run Stirling-PDF ### Step 7: Run Stirling-PDF
Those who have pushed to the root directory, run the following commands:
```bash ```bash
./gradlew bootRun ./gradlew bootRun
or or
java -jar /opt/Stirling-PDF/Stirling-PDF-*.jar java -jar /opt/Stirling-PDF/Stirling-PDF-*.jar
``` ```
Since libreoffice, soffice, and conversion tools have their dbus_tmp_dir set as `dbus_tmp_dir="/run/user/$(id -u)/libreoffice-dbus"`, you might get the following error when using their endpoints:
```
[Thread-7] INFO s.s.SPDF.utils.ProcessExecutor - mkdir: cannot create directory /run/user/1501: Permission denied
```
To resolve this, before starting the Stirling-PDF, you have to set the environment variable to a directory you have write access to by using the following commands:
```bash
mkdir temp
export DBUS_SESSION_BUS_ADDRESS="unix:path=./temp"
./gradlew bootRun
or
java -jar ./Stirling-PDF-*.jar
```
### Step 8: Adding a Desktop icon ### Step 8: Adding a Desktop icon
This will add a modified Appstarter to your Appmenu. This will add a modified Appstarter to your Appmenu.
@ -202,7 +251,19 @@ EOF
Note: Currently the app will run in the background until manually closed. Note: Currently the app will run in the background until manually closed.
### Optional: Run Stirling-PDF as a service ### Optional: Changing the host and port of the application:
To override the default configuration, you can add the following to `/.git/Stirling-PDF/configs/custom_settings.yml` file:
```bash
server:
host: 0.0.0.0
port: 3000
```
**Note:** This file is created after the first application launch. To have it before that, you can create the directory and add the file yourself.
### Optional: Run Stirling-PDF as a service (requires root).
First create a .env file, where you can store environment variables: First create a .env file, where you can store environment variables:
``` ```
@ -239,6 +300,7 @@ WantedBy=multi-user.target
``` ```
Notify systemd that it has to rebuild its internal service database (you have to run this command every time you make a change in the service file): Notify systemd that it has to rebuild its internal service database (you have to run this command every time you make a change in the service file):
``` ```
sudo systemctl daemon-reload sudo systemctl daemon-reload
``` ```

View File

@ -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) [![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. 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 \ -v /location/of/logs:/logs \
-e DOCKER_ENABLE_SECURITY=false \ -e DOCKER_ENABLE_SECURITY=false \
-e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \ -e INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false \
-e LANGS=en_GB \
--name stirling-pdf \ --name stirling-pdf \
frooodle/s-pdf:latest frooodle/s-pdf:latest
@ -147,6 +148,7 @@ services:
environment: environment:
- DOCKER_ENABLE_SECURITY=false - DOCKER_ENABLE_SECURITY=false
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false - INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
- LANGS=en_GB
``` ```
Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman". Note: Podman is CLI-compatible with Docker, so simply replace "docker" with "podman".
@ -163,31 +165,31 @@ Stirling PDF currently supports 27!
| ------------------------------------------- | -------------------------------------- | | ------------------------------------------- | -------------------------------------- |
| English (English) (en_GB) | ![100%](https://geps.dev/progress/100) | | English (English) (en_GB) | ![100%](https://geps.dev/progress/100) |
| English (US) (en_US) | ![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) | | Arabic (العربية) (ar_AR) | ![42%](https://geps.dev/progress/42) |
| German (Deutsch) (de_DE) | ![98%](https://geps.dev/progress/98) | | German (Deutsch) (de_DE) | ![99%](https://geps.dev/progress/99) |
| French (Français) (fr_FR) | ![94%](https://geps.dev/progress/94) | | French (Français) (fr_FR) | ![91%](https://geps.dev/progress/91) |
| Spanish (Español) (es_ES) | ![95%](https://geps.dev/progress/95) | | Spanish (Español) (es_ES) | ![99%](https://geps.dev/progress/99) |
| Simplified Chinese (简体中文) (zh_CN) | ![99%](https://geps.dev/progress/99) | | Simplified Chinese (简体中文) (zh_CN) | ![98%](https://geps.dev/progress/98) |
| Traditional Chinese (繁體中文) (zh_TW) | ![99%](https://geps.dev/progress/99) | | Traditional Chinese (繁體中文) (zh_TW) | ![98%](https://geps.dev/progress/98) |
| Catalan (Català) (ca_CA) | ![65%](https://geps.dev/progress/65) | | Catalan (Català) (ca_CA) | ![51%](https://geps.dev/progress/51) |
| Italian (Italiano) (it_IT) | ![98%](https://geps.dev/progress/98) | | Italian (Italiano) (it_IT) | ![99%](https://geps.dev/progress/99) |
| Swedish (Svenska) (sv_SE) | ![58%](https://geps.dev/progress/58) | | Swedish (Svenska) (sv_SE) | ![42%](https://geps.dev/progress/42) |
| Polish (Polski) (pl_PL) | ![60%](https://geps.dev/progress/60) | | Polish (Polski) (pl_PL) | ![44%](https://geps.dev/progress/44) |
| Romanian (Română) (ro_RO) | ![58%](https://geps.dev/progress/58) | | Romanian (Română) (ro_RO) | ![41%](https://geps.dev/progress/41) |
| Korean (한국어) (ko_KR) | ![94%](https://geps.dev/progress/94) | | Korean (한국어) (ko_KR) | ![91%](https://geps.dev/progress/91) |
| Portuguese Brazilian (Português) (pt_BR) | ![74%](https://geps.dev/progress/74) | | Portuguese Brazilian (Português) (pt_BR) | ![64%](https://geps.dev/progress/64) |
| Russian (Русский) (ru_RU) | ![94%](https://geps.dev/progress/94) | | Russian (Русский) (ru_RU) | ![90%](https://geps.dev/progress/90) |
| Basque (Euskara) (eu_ES) | ![76%](https://geps.dev/progress/76) | | Basque (Euskara) (eu_ES) | ![66%](https://geps.dev/progress/66) |
| Japanese (日本語) (ja_JP) | ![94%](https://geps.dev/progress/94) | | Japanese (日本語) (ja_JP) | ![91%](https://geps.dev/progress/91) |
| Dutch (Nederlands) (nl_NL) | ![92%](https://geps.dev/progress/92) | | Dutch (Nederlands) (nl_NL) | ![88%](https://geps.dev/progress/88) |
| Greek (Ελληνικά) (el_GR) | ![92%](https://geps.dev/progress/92) | | Greek (Ελληνικά) (el_GR) | ![89%](https://geps.dev/progress/89) |
| Turkish (Türkçe) (tr_TR) | ![99%](https://geps.dev/progress/99) | | Turkish (Türkçe) (tr_TR) | ![99%](https://geps.dev/progress/99) |
| Indonesia (Bahasa Indonesia) (id_ID) | ![87%](https://geps.dev/progress/87) | | Indonesia (Bahasa Indonesia) (id_ID) | ![82%](https://geps.dev/progress/82) |
| Hindi (हिंदी) (hi_IN) | ![88%](https://geps.dev/progress/88) | | Hindi (हिंदी) (hi_IN) | ![82%](https://geps.dev/progress/82) |
| Hungarian (Magyar) (hu_HU) | ![87%](https://geps.dev/progress/87) | | Hungarian (Magyar) (hu_HU) | ![81%](https://geps.dev/progress/81) |
| Bulgarian (Български) (bg_BG) | ![82%](https://geps.dev/progress/82) | | Bulgarian (Български) (bg_BG) | ![98%](https://geps.dev/progress/98) |
| Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![89%](https://geps.dev/progress/89) | | Sebian Latin alphabet (Srpski) (sr_LATN_RS) | ![84%](https://geps.dev/progress/84) |
| Ukrainian (Українська) (uk_UA) | ![98%](https://geps.dev/progress/98) | | Ukrainian (Українська) (uk_UA) | ![90%](https://geps.dev/progress/90) |
## Contributing (creating issues, translations, fixing bugs, etc.) ## Contributing (creating issues, translations, fixing bugs, etc.)
@ -199,7 +201,7 @@ Stirling PDF allows easy customization of the app.
Includes things like Includes things like
- Custom application name - 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`` 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 This file is located in the ``/configs`` directory and follows standard YAML formatting
@ -225,6 +227,9 @@ system:
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc) 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 googlevisibility: false # 'true' to allow Google visibility (via robots.txt), 'false' to disallow
customStaticFilePath: '/customFiles/static/' # Directory path for custom static files 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: #ui:
# appName: exampleAppName # Application's visible name # appName: exampleAppName # Application's visible name
@ -239,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 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 ### 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) - 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)
@ -250,13 +257,13 @@ metrics:
- ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values - ``SYSTEM_CONNECTIONTIMEOUTMINUTES`` to set custom connection timeout values
- ``DOCKER_ENABLE_SECURITY`` to tell docker to download security jar (required as true for auth login) - ``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 - ``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 ## 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 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) [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 ## Login authentication
![stirling-login](images/login-light.png) ![stirling-login](images/login-light.png)
@ -264,7 +271,7 @@ For those wanting to use Stirling-PDFs backend API to link with their own custom
### Prerequisites: ### Prerequisites:
- User must have the folder ./configs volumed within docker so that it is retained during updates. - User must have the folder ./configs volumed within docker so that it is retained during updates.
- Docker uses must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables. - Docker users must download the security jar version by setting ``DOCKER_ENABLE_SECURITY`` to ``true`` in environment variables.
- Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true`` - Then either enable login via the settings.yml file or via setting ``SECURITY_ENABLE_LOGIN`` to ``true``
- Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation). - Now the initial user will be generated with username ``admin`` and password ``stirling``. On login you will be forced to change the password to a new one. You can also use the environment variables ``SECURITY_INITIALLOGIN_USERNAME`` and ``SECURITY_INITIALLOGIN_PASSWORD`` to set your own straight away (Recommended to remove them after user creation).

View File

@ -12,7 +12,7 @@ plugins {
import com.github.jk1.license.render.* import com.github.jk1.license.render.*
group = 'stirling.software' group = 'stirling.software'
version = '0.22.8' version = '0.23.2'
sourceCompatibility = '17' sourceCompatibility = '17'
repositories { repositories {
@ -99,6 +99,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security:3.2.4' implementation 'org.springframework.boot:spring-boot-starter-security:3.2.4'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.1.2.RELEASE' 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-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 //2.2.x requires rebuild of DB file.. need migration path
implementation "com.h2database:h2:2.1.214" implementation "com.h2database:h2:2.1.214"

View File

@ -1,5 +1,5 @@
apiVersion: v2 apiVersion: v2
appVersion: 0.22.8 appVersion: 0.23.2
description: locally hosted web application that allows you to perform various operations description: locally hosted web application that allows you to perform various operations
on PDF files on PDF files
home: https://github.com/Stirling-Tools/Stirling-PDF home: https://github.com/Stirling-Tools/Stirling-PDF

View 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

View File

@ -21,6 +21,8 @@ services:
environment: environment:
DOCKER_ENABLE_SECURITY: "false" DOCKER_ENABLE_SECURITY: "false"
SECURITY_ENABLELOGIN: "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 SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: Stirling-PDF UI_APPNAME: Stirling-PDF
UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest UI_HOMEDESCRIPTION: Demo site for Stirling-PDF Latest

View File

@ -10,49 +10,77 @@ Author: Ludy87
Example: Example:
To use this script, simply run it from command line: To use this script, simply run it from command line:
$ python counter_translation.py $ python counter_translation.py
""" """ # noqa: D205
import os
import glob import glob
import os
import re import re
from typing import List, Tuple
import tomlkit
import tomlkit.toml_file
def write_readme(progress_list: List[Tuple[str, int]]) -> None: 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.
Updates the progress status in the README.md file based 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. on the provided progress list.
Parameters: 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. language and progress percentage.
Returns: Returns:
None None
""" """ # noqa: D205
with open("README.md", "r", encoding="utf-8") as file: with open("README.md", encoding="utf-8") as file:
content = file.read() content = file.readlines()
lines = content.split("\n") for i, line in enumerate(content[2:], start=2):
for i, line in enumerate(lines[2:], start=2):
for progress in progress_list: for progress in progress_list:
language, value = progress language, value = progress
if language in line: if language in line:
match = re.search(r"\!\[(\d+(\.\d+)?)%\]\(.*\)", line) if match := re.search(r"\!\[(\d+(\.\d+)?)%\]\(.*\)", line):
if match: content[i] = line.replace(
lines[i] = line.replace(
match.group(0), match.group(0),
f"![{value}%](https://geps.dev/progress/{value})", f"![{value}%](https://geps.dev/progress/{value})",
) )
new_content = "\n".join(lines)
with open("README.md", "w", encoding="utf-8") as file: 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]]: def compare_files(default_file_path, file_paths, translation_status_file) -> list[tuple[str, int]]:
""" """Compares the default properties file with other
Compares the default properties file with other
properties files in the directory. properties files in the directory.
Parameters: 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. files_directory (str): The directory containing other properties files.
Returns: Returns:
List[Tuple[str, int]]: A list of tuples containing list[tuple[str, int]]: A list of tuples containing
language and progress percentage. language and progress percentage.
""" """ # noqa: D205
file_paths = glob.glob(os.path.join(files_directory, "messages_*.properties")) num_lines = sum(
num_lines = sum(1 for _ in open(default_file_path, encoding="utf-8")) 1 for line in open(default_file_path, encoding="utf-8") if line.strip() and not line.strip().startswith("#")
)
result_list = [] 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: for file_path in file_paths:
language = ( language = os.path.basename(file_path).split("messages_", 1)[1].split(".properties", 1)[0]
os.path.basename(file_path)
.split("messages_", 1)[1]
.split(".properties", 1)[0]
)
fails = 0 fails = 0
if "en_GB" in language or "en_US" in language: 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)) result_list.append(("en_US", 100))
continue continue
with open(default_file_path, "r", encoding="utf-8") as default_file, open( if language not in sort_translation_status:
file_path, "r", encoding="utf-8" sort_translation_status[language] = tomlkit.table()
) as file:
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): for _ in range(5):
next(default_file) next(default_file)
try: try:
@ -91,24 +133,47 @@ def compare_files(default_file_path, files_directory) -> List[Tuple[str, int]]:
except StopIteration: except StopIteration:
fails = num_lines fails = num_lines
for _, (line_default, line_file) in enumerate( for line_num, (line_default, line_file) in enumerate(zip(default_file, file), start=6):
zip(default_file, file), start=6
):
try: 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 ( if (
line_default.split("=", 1)[1].strip() default_value.strip() == file_value.strip()
== line_file.split("=", 1)[1].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 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: except IndexError:
pass pass
print(f"{language}: {fails} out of {num_lines} lines are not translated.")
result_list.append( result_list.append(
( (
language, language,
int((num_lines - fails) * 100 / num_lines), 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 = list(set(result_list))
unique_data.sort(key=lambda x: x[1], reverse=True) 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__": if __name__ == "__main__":
directory = os.path.join(os.getcwd(), "src", "main", "resources") 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") 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))

View File

@ -1,25 +1,30 @@
#!/bin/sh #!/bin/bash
# Update the user and group IDs as per environment variables # Update the user and group IDs as per environment variables
if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then if [ ! -z "$PUID" ] && [ "$PUID" != "$(id -u stirlingpdfuser)" ]; then
usermod -o -u "$PUID" stirlingpdfuser || true usermod -o -u "$PUID" stirlingpdfuser || true
fi fi
if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then if [ ! -z "$PGID" ] && [ "$PGID" != "$(getent group stirlingpdfgroup | cut -d: -f3)" ]; then
groupmod -o -g "$PGID" stirlingpdfgroup || true groupmod -o -g "$PGID" stirlingpdfgroup || true
fi fi
umask "$UMASK" || true umask "$UMASK" || true
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then
apk add --no-cache calibre@testing apk add --no-cache calibre@testing
fi fi
/scripts/download-security-jar.sh /scripts/download-security-jar.sh
if [[ -n "$LANGS" ]]; then
/scripts/installFonts.sh $LANGS
fi
echo "Setting permissions and ownership for necessary directories..." 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 # Attempt to change ownership of directories and files
chmod -R 755 /logs /scripts /usr/share/fonts/opentype/noto /usr/share/tessdata /configs /customFiles /pipeline /app.jar || true 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 # If chown succeeds, execute the command as stirlingpdfuser
exec su-exec stirlingpdfuser "$@" exec su-exec stirlingpdfuser "$@"
else else

View File

@ -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; cp -r /usr/share/tesseract-ocr/5/tessdata/* /usr/share/tessdata || true;
fi 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 # Check if TESSERACT_LANGS environment variable is set and is not empty
if [[ -n "$TESSERACT_LANGS" ]]; then if [[ -n "$TESSERACT_LANGS" ]]; then
# Convert comma-separated values to a space-separated list # Convert comma-separated values to a space-separated list
@ -40,20 +28,4 @@ if [[ -n "$TESSERACT_LANGS" ]]; then
done done
fi fi
if [[ "$INSTALL_BOOK_AND_ADVANCED_HTML_OPS" == "true" ]]; then /scripts/init-without-ocr.sh "$@"
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

67
scripts/installFonts.sh Normal file
View 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

View 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',
]

View File

@ -5,6 +5,8 @@ import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -62,16 +64,39 @@ public class SPdfApplication {
} }
public static void main(String[] args) throws IOException, InterruptedException { public static void main(String[] args) throws IOException, InterruptedException {
SpringApplication app = new SpringApplication(SPdfApplication.class); SpringApplication app = new SpringApplication(SPdfApplication.class);
app.addInitializers(new ConfigInitializer()); app.addInitializers(new ConfigInitializer());
Map<String, String> propertyFiles = new HashMap<>();
// stirling pdf settings file
if (Files.exists(Paths.get("configs/settings.yml"))) { if (Files.exists(Paths.get("configs/settings.yml"))) {
app.setDefaultProperties( propertyFiles.put("spring.config.additional-location", "file:configs/settings.yml");
Collections.singletonMap(
"spring.config.additional-location", "file:configs/settings.yml"));
} else { } else {
logger.warn( logger.warn(
"External configuration file 'configs/settings.yml' does not exist. Using default configuration and environment configuration instead."); "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); app.run(args);
try { try {

View File

@ -6,18 +6,35 @@ import java.nio.file.Paths;
import java.util.Properties; import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.thymeleaf.spring6.SpringTemplateEngine;
import stirling.software.SPDF.model.ApplicationProperties; import stirling.software.SPDF.model.ApplicationProperties;
@Configuration @Configuration
@Lazy
public class AppConfig { public class AppConfig {
@Autowired ApplicationProperties applicationProperties; @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") @Bean(name = "loginEnabled")
public boolean loginEnabled() { public boolean loginEnabled() {
return applicationProperties.getSecurity().getEnableLogin(); return applicationProperties.getSecurity().getEnableLogin();
@ -85,4 +102,10 @@ public class AppConfig {
} }
return "true".equalsIgnoreCase(installOps); return "true".equalsIgnoreCase(installOps);
} }
@ConditionalOnMissingClass("stirling.software.SPDF.config.security.SecurityConfiguration")
@Bean(name = "activSecurity")
public boolean missingActivSecurity() {
return false;
}
} }

View File

@ -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;
}
}

View File

@ -1,20 +1,18 @@
package stirling.software.SPDF.config; package stirling.software.SPDF.config;
import java.io.BufferedReader;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
@ -26,12 +24,12 @@ public class ConfigInitializer
public void initialize(ConfigurableApplicationContext applicationContext) { public void initialize(ConfigurableApplicationContext applicationContext) {
try { try {
ensureConfigExists(); ensureConfigExists();
} catch (IOException e) { } catch (Exception e) {
throw new RuntimeException("Failed to initialize application configuration", 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 // Define the path to the external config directory
Path destPath = Paths.get("configs", "settings.yml"); Path destPath = Paths.get("configs", "settings.yml");
@ -51,170 +49,154 @@ public class ConfigInitializer
} }
} }
} else { } else {
// If user file exists, we need to merge it with the template from the classpath Path templatePath =
List<String> templateLines; Paths.get(
try (InputStream in = getClass()
getClass().getClassLoader().getResourceAsStream("settings.yml.template")) { .getClassLoader()
templateLines = .getResource("settings.yml.template")
new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)) .toURI());
.lines() Path userPath = Paths.get("configs", "settings.yml");
.collect(Collectors.toList());
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);
} }
mergeYamlFiles(templateLines, destPath, destPath); Path customSettingsPath = Paths.get("configs", "custom_settings.yml");
if (!Files.exists(customSettingsPath)) {
Files.createFile(customSettingsPath);
} }
} }
public void mergeYamlFiles(List<String> templateLines, Path userFilePath, Path outputPath) private static Map<String, String> extractEntries(List<String> lines) {
throws IOException { Map<String, String> entries = new HashMap<>();
List<String> userLines = Files.readAllLines(userFilePath); StringBuilder currentEntry = new StringBuilder();
String currentKey = null;
int blockIndent = -1;
for (String line : lines) {
if (line.trim().isEmpty()) {
if (currentKey != null) {
currentEntry.append(line).append("\n");
}
continue;
}
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();
}
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");
}
}
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<>(); List<String> mergedLines = new ArrayList<>();
boolean insideAutoGenerated = false; Set<String> handledKeys = new HashSet<>();
boolean beforeFirstKey = true;
Function<String, Boolean> isCommented = line -> line.trim().startsWith("#"); String currentBlockKey = null;
Function<String, String> extractKey = int blockIndent = -1;
line -> {
String[] parts = line.split(":");
return parts.length > 0 ? parts[0].trim().replace("#", "").trim() : "";
};
Function<String, Integer> getIndentationLevel = for (String line : templateLines) {
line -> { if (line.trim().isEmpty()) {
mergedLines.add(line);
continue;
}
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);
}
}
return mergedLines;
}
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();
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;
}
// 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; int count = 0;
for (char ch : line.toCharArray()) { for (char ch : line.toCharArray()) {
if (ch == ' ') count++; if (ch == ' ') count++;
else break; else break;
} }
return count; return count;
};
Set<String> userKeys = userLines.stream().map(extractKey).collect(Collectors.toSet());
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;
mergedLines.add(line);
continue;
}
if (beforeFirstKey && (isCommented.apply(line) || line.trim().isEmpty())) {
// Handle top comments and empty lines before the first key.
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);
}
// 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);
// 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;
}
}
}
return isChild; // Return true if the line is not a child of any entry in templateLines
} }
} }

View File

@ -146,7 +146,6 @@ public class EndpointConfiguration {
addEndpointToGroup("CLI", "xlsx-to-pdf"); addEndpointToGroup("CLI", "xlsx-to-pdf");
addEndpointToGroup("CLI", "pdf-to-word"); addEndpointToGroup("CLI", "pdf-to-word");
addEndpointToGroup("CLI", "pdf-to-presentation"); addEndpointToGroup("CLI", "pdf-to-presentation");
addEndpointToGroup("CLI", "pdf-to-text");
addEndpointToGroup("CLI", "pdf-to-html"); addEndpointToGroup("CLI", "pdf-to-html");
addEndpointToGroup("CLI", "pdf-to-xml"); addEndpointToGroup("CLI", "pdf-to-xml");
addEndpointToGroup("CLI", "ocr-pdf"); addEndpointToGroup("CLI", "ocr-pdf");
@ -154,6 +153,7 @@ public class EndpointConfiguration {
addEndpointToGroup("CLI", "url-to-pdf"); addEndpointToGroup("CLI", "url-to-pdf");
addEndpointToGroup("CLI", "book-to-pdf"); addEndpointToGroup("CLI", "book-to-pdf");
addEndpointToGroup("CLI", "pdf-to-book"); addEndpointToGroup("CLI", "pdf-to-book");
addEndpointToGroup("CLI", "pdf-to-rtf");
// Calibre // Calibre
addEndpointToGroup("Calibre", "book-to-pdf"); addEndpointToGroup("Calibre", "book-to-pdf");
@ -175,7 +175,7 @@ public class EndpointConfiguration {
addEndpointToGroup("LibreOffice", "xlsx-to-pdf"); addEndpointToGroup("LibreOffice", "xlsx-to-pdf");
addEndpointToGroup("LibreOffice", "pdf-to-word"); addEndpointToGroup("LibreOffice", "pdf-to-word");
addEndpointToGroup("LibreOffice", "pdf-to-presentation"); addEndpointToGroup("LibreOffice", "pdf-to-presentation");
addEndpointToGroup("LibreOffice", "pdf-to-text"); addEndpointToGroup("LibreOffice", "pdf-to-rtf");
addEndpointToGroup("LibreOffice", "pdf-to-html"); addEndpointToGroup("LibreOffice", "pdf-to-html");
addEndpointToGroup("LibreOffice", "pdf-to-xml"); addEndpointToGroup("LibreOffice", "pdf-to-xml");
@ -218,6 +218,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "overlay-pdf"); addEndpointToGroup("Java", "overlay-pdf");
addEndpointToGroup("Java", "split-pdf-by-sections"); addEndpointToGroup("Java", "split-pdf-by-sections");
addEndpointToGroup("Java", REMOVE_BLANKS); addEndpointToGroup("Java", REMOVE_BLANKS);
addEndpointToGroup("Java", "pdf-to-text");
// Javascript // Javascript
addEndpointToGroup("Javascript", "pdf-organizer"); addEndpointToGroup("Javascript", "pdf-organizer");

View File

@ -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);
}
}

View File

@ -0,0 +1,7 @@
package stirling.software.SPDF.config;
public interface ShowAdminInterface {
default boolean getShowUpdateOnlyAdmins() {
return true;
}
}

View File

@ -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;
}
}

View File

@ -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");
}
}
}

View File

@ -50,7 +50,7 @@ public class InitialSecuritySetup {
@PostConstruct @PostConstruct
public void initSecretKey() throws IOException { public void initSecretKey() throws IOException {
String secretKey = applicationProperties.getAutomaticallyGenerated().getKey(); 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 secretKey = UUID.randomUUID().toString(); // Generating a random UUID as the secret key
saveKeyToConfig(secretKey); saveKeyToConfig(secretKey);
} }
@ -85,4 +85,16 @@ public class InitialSecuritySetup {
// Write back to the file // Write back to the file
Files.write(path, lines); 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;
}
}
} }

View File

@ -1,7 +1,11 @@
package stirling.software.SPDF.config.security; 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.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; 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.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy; 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.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy; 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.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl; import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; 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.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 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 jakarta.servlet.http.HttpSession;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.User;
import stirling.software.SPDF.repository.JPATokenRepositoryImpl; import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
@Configuration @Configuration
@ -42,6 +62,8 @@ public class SecurityConfiguration {
@Qualifier("loginEnabled") @Qualifier("loginEnabled")
public boolean loginEnabledValue; public boolean loginEnabledValue;
@Autowired ApplicationProperties applicationProperties;
@Autowired private UserAuthenticationFilter userAuthenticationFilter; @Autowired private UserAuthenticationFilter userAuthenticationFilter;
@Autowired private LoginAttemptService loginAttemptService; @Autowired private LoginAttemptService loginAttemptService;
@ -87,7 +109,7 @@ public class SecurityConfiguration {
logout -> logout ->
logout.logoutRequestMatcher( logout.logoutRequestMatcher(
new AntPathRequestMatcher("/logout")) new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout=true") .logoutSuccessHandler(new CustomLogoutSuccessHandler())
.invalidateHttpSession(true) // Invalidate session .invalidateHttpSession(true) // Invalidate session
.deleteCookies("JSESSIONID", "remember-me") .deleteCookies("JSESSIONID", "remember-me")
.addLogoutHandler( .addLogoutHandler(
@ -124,6 +146,7 @@ public class SecurityConfiguration {
: uri; : uri;
return trimmedUri.startsWith("/login") return trimmedUri.startsWith("/login")
|| trimmedUri.startsWith("/oauth")
|| trimmedUri.endsWith(".svg") || trimmedUri.endsWith(".svg")
|| trimmedUri.startsWith( || trimmedUri.startsWith(
"/register") "/register")
@ -140,6 +163,49 @@ public class SecurityConfiguration {
.authenticated()) .authenticated())
.userDetailsService(userDetailsService) .userDetailsService(userDetailsService)
.authenticationProvider(authenticationProvider()); .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 { } else {
http.csrf(csrf -> csrf.disable()) http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(authz -> authz.anyRequest().permitAll()); .authorizeHttpRequests(authz -> authz.anyRequest().permitAll());
@ -148,6 +214,65 @@ public class SecurityConfiguration {
return http.build(); 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 @Bean
public IPRateLimitingFilter rateLimitingFilter() { public IPRateLimitingFilter rateLimitingFilter() {
int maxRequestsPerIp = 1000000; // Example limit TODO add config level int maxRequestsPerIp = 1000000; // Example limit TODO add config level
@ -166,4 +291,9 @@ public class SecurityConfiguration {
public PersistentTokenRepository persistentTokenRepository() { public PersistentTokenRepository persistentTokenRepository() {
return new JPATokenRepositoryImpl(); return new JPATokenRepositoryImpl();
} }
@Bean
public boolean activSecurity() {
return true;
}
} }

View File

@ -1,5 +1,6 @@
package stirling.software.SPDF.config.security; package stirling.software.SPDF.config.security;
import io.github.pixee.security.Newlines;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import java.util.Map; import java.util.Map;
@ -125,12 +126,12 @@ public class UserBasedRateLimitingFilter extends OncePerRequestFilter {
ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1); ConsumptionProbe probe = userBucket.tryConsumeAndReturnRemaining(1);
if (probe.isConsumed()) { if (probe.isConsumed()) {
response.setHeader("X-Rate-Limit-Remaining", Long.toString(probe.getRemainingTokens())); response.setHeader("X-Rate-Limit-Remaining", Newlines.stripAll(Long.toString(probe.getRemainingTokens())));
filterChain.doFilter(request, response); filterChain.doFilter(request, response);
} else { } else {
long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000; long waitForRefill = probe.getNanosToWaitForRefill() / 1_000_000_000;
response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
response.setHeader("X-Rate-Limit-Retry-After-Seconds", String.valueOf(waitForRefill)); response.setHeader("X-Rate-Limit-Retry-After-Seconds", Newlines.stripAll(String.valueOf(waitForRefill)));
response.getWriter().write("Rate limit exceeded for POST requests."); response.getWriter().write("Rate limit exceeded for POST requests.");
} }
} }

View File

@ -21,6 +21,7 @@ import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
import stirling.software.SPDF.model.Authority; import stirling.software.SPDF.model.Authority;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
import stirling.software.SPDF.repository.AuthorityRepository;
import stirling.software.SPDF.repository.UserRepository; import stirling.software.SPDF.repository.UserRepository;
@Service @Service
@ -28,8 +29,28 @@ public class UserService implements UserServiceInterface {
@Autowired private UserRepository userRepository; @Autowired private UserRepository userRepository;
@Autowired private AuthorityRepository authorityRepository;
@Autowired private PasswordEncoder passwordEncoder; @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) { public Authentication getAuthentication(String apiKey) {
User user = getUserByApiKey(apiKey); User user = getUserByApiKey(apiKey);
if (user == null) { if (user == null) {
@ -184,6 +205,10 @@ public class UserService implements UserServiceInterface {
return userRepository.findByUsernameIgnoreCase(username); return userRepository.findByUsernameIgnoreCase(username);
} }
public Authority findRole(User user) {
return authorityRepository.findByUserId(user.getId());
}
public void changeUsername(User user, String newUsername) { public void changeUsername(User user, String newUsername) {
user.setUsername(newUsername); user.setUsername(newUsername);
userRepository.save(user); userRepository.save(user);
@ -199,6 +224,12 @@ public class UserService implements UserServiceInterface {
userRepository.save(user); 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) { public boolean isPasswordCorrect(User user, String currentPassword) {
return passwordEncoder.matches(currentPassword, user.getPassword()); return passwordEncoder.matches(currentPassword, user.getPassword());
} }

View File

@ -4,8 +4,6 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream; import java.util.zip.ZipOutputStream;
@ -41,69 +39,114 @@ 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") + " 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) public ResponseEntity<byte[]> autoSplitPdf(@ModelAttribute SplitPdfBySizeOrCountRequest request)
throws Exception { throws Exception {
List<ByteArrayOutputStream> splitDocumentsBoas = new ArrayList<ByteArrayOutputStream>();
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
PDDocument sourceDocument = Loader.loadPDF(file.getBytes()); Path zipFile = Files.createTempFile("split_documents", ".zip");
String filename =
Filenames.toSimpleFileName(file.getOriginalFilename())
.replaceFirst("[.][^.]+$", "");
byte[] data = null;
try (ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipFile));
PDDocument sourceDocument = Loader.loadPDF(file.getBytes())) {
// 0 = size, 1 = page count, 2 = doc count
int type = request.getSplitType(); int type = request.getSplitType();
String value = request.getSplitValue(); String value = request.getSplitValue();
if (type == 0) { // Split by size if (type == 0) {
long maxBytes = GeneralUtils.convertSizeToBytes(value); 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.deleteIfExists(zipFile);
}
return WebResponseUtils.bytesToWebResponse(
data, filename + ".zip", MediaType.APPLICATION_OCTET_STREAM);
}
private void handleSplitBySize(
PDDocument sourceDocument, long maxBytes, ZipOutputStream zipOut, String baseFilename)
throws IOException {
long currentSize = 0; long currentSize = 0;
PDDocument currentDoc = new PDDocument(); PDDocument currentDoc = new PDDocument();
int fileIndex = 1;
for (PDPage page : sourceDocument.getPages()) { for (int pageIndex = 0; pageIndex < sourceDocument.getNumberOfPages(); pageIndex++) {
PDPage page = sourceDocument.getPage(pageIndex);
ByteArrayOutputStream pageOutputStream = new ByteArrayOutputStream(); ByteArrayOutputStream pageOutputStream = new ByteArrayOutputStream();
PDDocument tempDoc = new PDDocument();
tempDoc.addPage(page); try (PDDocument tempDoc = new PDDocument()) {
PDPage importedPage = tempDoc.importPage(page); // This creates a new PDPage object
tempDoc.save(pageOutputStream); tempDoc.save(pageOutputStream);
tempDoc.close(); }
long pageSize = pageOutputStream.size(); long pageSize = pageOutputStream.size();
if (currentSize + pageSize > maxBytes) { if (currentSize + pageSize > maxBytes) {
// Save and reset current document if (currentDoc.getNumberOfPages() > 0) {
splitDocumentsBoas.add(currentDocToByteArray(currentDoc)); saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
currentDoc.close(); // Make sure to close the document
currentDoc = new PDDocument(); currentDoc = new PDDocument();
currentSize = 0; currentSize = 0;
} }
}
currentDoc.addPage(page); PDPage newPage = new PDPage(page.getCOSObject()); // Re-create the page
currentDoc.addPage(newPage);
currentSize += pageSize; currentSize += pageSize;
} }
// Add the last document if it contains any pages
if (currentDoc.getPages().getCount() != 0) { if (currentDoc.getNumberOfPages() != 0) {
splitDocumentsBoas.add(currentDocToByteArray(currentDoc)); saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
currentDoc.close();
} }
} else if (type == 1) { // Split by page count }
int pageCount = Integer.parseInt(value);
private void handleSplitByPageCount(
PDDocument sourceDocument, int pageCount, ZipOutputStream zipOut, String baseFilename)
throws IOException {
int currentPageCount = 0; int currentPageCount = 0;
PDDocument currentDoc = new PDDocument(); PDDocument currentDoc = new PDDocument();
int fileIndex = 1;
for (PDPage page : sourceDocument.getPages()) { for (PDPage page : sourceDocument.getPages()) {
currentDoc.addPage(page); currentDoc.addPage(page);
currentPageCount++; currentPageCount++;
if (currentPageCount == pageCount) { if (currentPageCount == pageCount) {
// Save and reset current document // Save and reset current document
splitDocumentsBoas.add(currentDocToByteArray(currentDoc)); saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
currentDoc = new PDDocument(); currentDoc = new PDDocument();
currentPageCount = 0; currentPageCount = 0;
} }
} }
// Add the last document if it contains any pages // Add the last document if it contains any pages
if (currentDoc.getPages().getCount() != 0) { if (currentDoc.getPages().getCount() != 0) {
splitDocumentsBoas.add(currentDocToByteArray(currentDoc)); saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
} }
} else if (type == 2) { // Split by doc count }
int documentCount = Integer.parseInt(value);
private void handleSplitByDocCount(
PDDocument sourceDocument,
int documentCount,
ZipOutputStream zipOut,
String baseFilename)
throws IOException {
int totalPageCount = sourceDocument.getNumberOfPages(); int totalPageCount = sourceDocument.getNumberOfPages();
int pagesPerDocument = totalPageCount / documentCount; int pagesPerDocument = totalPageCount / documentCount;
int extraPages = totalPageCount % documentCount; int extraPages = totalPageCount % documentCount;
int currentPageIndex = 0; int currentPageIndex = 0;
int fileIndex = 1;
for (int i = 0; i < documentCount; i++) { for (int i = 0; i < documentCount; i++) {
PDDocument currentDoc = new PDDocument(); PDDocument currentDoc = new PDDocument();
int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0); int pagesToAdd = pagesPerDocument + (i < extraPages ? 1 : 0);
@ -112,46 +155,21 @@ public class SplitPdfBySizeController {
currentDoc.addPage(sourceDocument.getPage(currentPageIndex++)); currentDoc.addPage(sourceDocument.getPage(currentPageIndex++));
} }
splitDocumentsBoas.add(currentDocToByteArray(currentDoc)); saveDocumentToZip(currentDoc, zipOut, baseFilename, fileIndex++);
} }
} else {
throw new IllegalArgumentException("Invalid argument for split type");
} }
sourceDocument.close(); 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
Path zipFile = Files.createTempFile("split_documents", ".zip"); // Create a new zip entry
String filename = ZipEntry zipEntry = new ZipEntry(baseFilename + "_" + index + ".pdf");
Filenames.toSimpleFileName(file.getOriginalFilename()) zipOut.putNextEntry(zipEntry);
.replaceFirst("[.][^.]+$", ""); zipOut.write(outStream.toByteArray());
byte[] data;
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();
ZipEntry pdfEntry = new ZipEntry(fileName);
zipOut.putNextEntry(pdfEntry);
zipOut.write(pdf);
zipOut.closeEntry(); zipOut.closeEntry();
} }
} catch (Exception e) {
e.printStackTrace();
} finally {
data = Files.readAllBytes(zipFile);
Files.delete(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;
}
} }

View File

@ -227,6 +227,45 @@ public class UserController {
return new RedirectView("/addUsers"); // Redirect to account page after adding the user 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')") @PreAuthorize("hasRole('ROLE_ADMIN')")
@PostMapping("/admin/deleteUser/{username}") @PostMapping("/admin/deleteUser/{username}")
public RedirectView deleteUser( public RedirectView deleteUser(

View File

@ -6,10 +6,6 @@ import java.net.URLConnection;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; 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.MediaType;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
@ -39,7 +35,7 @@ public class ConvertImgPDFController {
summary = "Convert PDF to image(s)", summary = "Convert PDF to image(s)",
description = 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") "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 { throws IOException {
MultipartFile file = request.getFileInput(); MultipartFile file = request.getFileInput();
String imageFormat = request.getImageFormat(); String imageFormat = request.getImageFormat();
@ -76,22 +72,15 @@ public class ConvertImgPDFController {
// TODO Auto-generated catch block // TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
if (singleImage) { if (singleImage) {
HttpHeaders headers = new HttpHeaders(); String docName = filename + "." + imageFormat;
headers.setContentType(MediaType.parseMediaType(getMediaType(imageFormat))); MediaType mediaType = MediaType.parseMediaType(getMediaType(imageFormat));
ResponseEntity<Resource> response = return WebResponseUtils.bytesToWebResponse(result, docName, mediaType);
new ResponseEntity<>(new ByteArrayResource(result), headers, HttpStatus.OK);
return response;
} else { } else {
ByteArrayResource resource = new ByteArrayResource(result); String zipFilename = filename + "_convertedToImages.zip";
// return the Resource in the response return WebResponseUtils.bytesToWebResponse(
return ResponseEntity.ok() result, zipFilename, MediaType.APPLICATION_OCTET_STREAM);
.header(
HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=" + filename + "_convertedToImages.zip")
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.contentLength(resource.contentLength())
.body(resource);
} }
} }

View File

@ -16,7 +16,7 @@ import io.github.pixee.security.Filenames;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; 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;
import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult; import stirling.software.SPDF.utils.ProcessExecutor.ProcessExecutorResult;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@ -31,8 +31,10 @@ public class ConvertPDFToPDFA {
summary = "Convert a PDF to a PDF/A", summary = "Convert a PDF to a PDF/A",
description = 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") "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(); MultipartFile inputFile = request.getFileInput();
String outputFormat = request.getOutputFormat();
// Save the uploaded file to a temporary location // Save the uploaded file to a temporary location
Path tempInputFile = Files.createTempFile("input_", ".pdf"); Path tempInputFile = Files.createTempFile("input_", ".pdf");
@ -47,7 +49,7 @@ public class ConvertPDFToPDFA {
command.add("--skip-text"); command.add("--skip-text");
command.add("--tesseract-timeout=0"); command.add("--tesseract-timeout=0");
command.add("--output-type"); command.add("--output-type");
command.add("pdfa"); command.add(outputFormat.toString());
command.add(tempInputFile.toString()); command.add(tempInputFile.toString());
command.add(tempOutputFile.toString()); command.add(tempOutputFile.toString());

View File

@ -1,27 +1,29 @@
package stirling.software.SPDF.controller.api.misc; package stirling.software.SPDF.controller.api.misc;
import java.awt.AlphaComposite;
import java.awt.BasicStroke;
import java.awt.Color; 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.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.image.AffineTransformOp; import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp; import java.awt.image.BufferedImageOp;
import java.awt.image.ConvolveOp; import java.awt.image.ConvolveOp;
import java.awt.image.Kernel; import java.awt.image.Kernel;
import java.awt.image.RescaleOp;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Random; import java.util.Random;
import javax.imageio.ImageIO;
import org.apache.pdfbox.Loader; import org.apache.pdfbox.Loader;
import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.graphics.image.JPEGFactory;
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.PDImageXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.ImageType; import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer; import org.apache.pdfbox.rendering.PDFRenderer;
@ -29,6 +31,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ModelAttribute; 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.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile; 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 io.swagger.v3.oas.annotations.tags.Tag;
import stirling.software.SPDF.model.api.PDFFile; import stirling.software.SPDF.model.api.PDFFile;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils; import stirling.software.SPDF.utils.WebResponseUtils;
@RestController @RestController
@ -48,98 +52,39 @@ public class FakeScanControllerWIP {
private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class); private static final Logger logger = LoggerFactory.getLogger(FakeScanControllerWIP.class);
// TODO // TODO finish
@PostMapping(consumes = "multipart/form-data", value = "/fake-scan")
@Hidden @Hidden
// @PostMapping(consumes = "multipart/form-data", value = "/fakeScan")
@Operation( @Operation(
summary = "Repair a PDF file", summary = "Repair a PDF file",
description = 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.") "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(); MultipartFile inputFile = request.getFileInput();
// Load the PDF document
PDDocument document = Loader.loadPDF(inputFile.getBytes()); PDDocument document = Loader.loadPDF(inputFile.getBytes());
PDFRenderer pdfRenderer = new PDFRenderer(document); PDFRenderer renderer = new PDFRenderer(document);
pdfRenderer.setSubsamplingAllowed(true); List<BufferedImage> images = new ArrayList<>();
for (int page = 0; page < document.getNumberOfPages(); ++page) { // Convert each page to an image
BufferedImage image = pdfRenderer.renderImageWithDPI(page, 300, ImageType.RGB); for (int i = 0; i < document.getNumberOfPages(); i++) {
ImageIO.write(image, "png", new File("scanned-" + (page + 1) + ".png")); BufferedImage image = renderer.renderImageWithDPI(i, 150, ImageType.GRAY);
images.add(processImage(image));
} }
document.close(); document.close();
// Constants // Create a new PDF document with the processed images
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();
}
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ByteArrayOutputStream baos = new ByteArrayOutputStream();
documentOut.save(baos); PDDocument newDocument = new PDDocument();
documentOut.close(); 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 // Return the optimized PDF as a response
String outputFilename = String outputFilename =
@ -148,4 +93,232 @@ public class FakeScanControllerWIP {
+ "_scanned.pdf"; + "_scanned.pdf";
return WebResponseUtils.boasToWebResponse(baos, outputFilename); 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;
}
} }

View File

@ -0,0 +1,84 @@
package stirling.software.SPDF.controller.api.misc;
import java.awt.image.BufferedImage;
import java.io.IOException;
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.graphics.image.JPEGFactory;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;
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.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.misc.FlattenRequest;
import stirling.software.SPDF.utils.PdfUtils;
import stirling.software.SPDF.utils.WebResponseUtils;
@RestController
@RequestMapping("/api/v1/misc")
@Tag(name = "Misc", description = "Miscellaneous APIs")
public class FlattenController {
@PostMapping(consumes = "multipart/form-data", value = "/flatten")
@Operation(
summary = "Flatten PDF form fields or full page",
description =
"Flattening just PDF form fields or converting each page to images to make text unselectable. Input: PDF, Output: PDF. Type: SISO")
public ResponseEntity<byte[]> flatten(@ModelAttribute FlattenRequest request) throws Exception {
MultipartFile file = request.getFileInput();
PDDocument document = Loader.loadPDF(file.getBytes());
PdfMetadata metadata = PdfUtils.extractMetadataFromPdf(document);
Boolean flattenOnlyForms = request.getFlattenOnlyForms();
if (Boolean.TRUE.equals(flattenOnlyForms)) {
PDAcroForm acroForm = document.getDocumentCatalog().getAcroForm();
if (acroForm != null) {
acroForm.flatten();
}
return WebResponseUtils.pdfDocToWebResponse(
document, Filenames.toSimpleFileName(file.getOriginalFilename()));
} else {
// flatten whole page aka convert each page to image and readd it (making text
// unselectable)
PDFRenderer pdfRenderer = new PDFRenderer(document);
PDDocument newDocument = new PDDocument();
int numPages = document.getNumberOfPages();
for (int i = 0; i < numPages; i++) {
try {
BufferedImage image = pdfRenderer.renderImageWithDPI(i, 300, ImageType.RGB);
PDPage page = new PDPage();
page.setMediaBox(document.getPage(i).getMediaBox());
newDocument.addPage(page);
try (PDPageContentStream contentStream =
new PDPageContentStream(newDocument, page)) {
PDImageXObject pdImage = JPEGFactory.createFromImage(newDocument, image);
float pageWidth = page.getMediaBox().getWidth();
float pageHeight = page.getMediaBox().getHeight();
contentStream.drawImage(pdImage, 0, 0, pageWidth, pageHeight);
}
} catch (IOException e) {
e.printStackTrace();
}
}
PdfUtils.setMetadataToPdf(newDocument, metadata);
return WebResponseUtils.pdfDocToWebResponse(
newDocument, Filenames.toSimpleFileName(file.getOriginalFilename()));
}
}
}

View File

@ -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);
}
}
}

View File

@ -139,25 +139,29 @@ public class SanitizeController {
for (PDPage page : allPages) { for (PDPage page : allPages) {
PDResources res = page.getResources(); PDResources res = page.getResources();
if (res != null && res.getCOSObject() != null) {
// Remove embedded files from the PDF
res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles")); res.getCOSObject().removeItem(COSName.getPDFName("EmbeddedFiles"));
} }
} }
}
private void sanitizeMetadata(PDDocument document) { private void sanitizeMetadata(PDDocument document) {
if (document.getDocumentCatalog() != null) {
PDMetadata metadata = document.getDocumentCatalog().getMetadata(); PDMetadata metadata = document.getDocumentCatalog().getMetadata();
if (metadata != null) { if (metadata != null) {
document.getDocumentCatalog().setMetadata(null); document.getDocumentCatalog().setMetadata(null);
} }
} }
}
private void sanitizeLinks(PDDocument document) throws IOException { private void sanitizeLinks(PDDocument document) throws IOException {
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
for (PDAnnotation annotation : page.getAnnotations()) { for (PDAnnotation annotation : page.getAnnotations()) {
if (annotation instanceof PDAnnotationLink) { if (annotation != null && annotation instanceof PDAnnotationLink) {
PDAction action = ((PDAnnotationLink) annotation).getAction(); 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); ((PDAnnotationLink) annotation).setAction(null);
} }
} }
@ -167,7 +171,11 @@ public class SanitizeController {
private void sanitizeFonts(PDDocument document) { private void sanitizeFonts(PDDocument document) {
for (PDPage page : document.getPages()) { for (PDPage page : document.getPages()) {
if (page != null
&& page.getResources() != null
&& page.getResources().getCOSObject() != null) {
page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font")); page.getResources().getCOSObject().removeItem(COSName.getPDFName("Font"));
} }
} }
} }
}

View File

@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication; import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.ui.Model; import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping; 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 io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import stirling.software.SPDF.model.ApplicationProperties;
import stirling.software.SPDF.model.Authority; import stirling.software.SPDF.model.Authority;
import stirling.software.SPDF.model.Role; import stirling.software.SPDF.model.Role;
import stirling.software.SPDF.model.User; import stirling.software.SPDF.model.User;
@ -28,12 +30,17 @@ import stirling.software.SPDF.repository.UserRepository;
@Tag(name = "Account Security", description = "Account Security APIs") @Tag(name = "Account Security", description = "Account Security APIs")
public class AccountWebController { public class AccountWebController {
@Autowired ApplicationProperties applicationProperties;
@GetMapping("/login") @GetMapping("/login")
public String login(HttpServletRequest request, Model model, Authentication authentication) { public String login(HttpServletRequest request, Model model, Authentication authentication) {
if (authentication != null && authentication.isAuthenticated()) { if (authentication != null && authentication.isAuthenticated()) {
return "redirect:/"; return "redirect:/";
} }
model.addAttribute(
"oAuth2Enabled", applicationProperties.getSecurity().getOAUTH2().getEnabled());
model.addAttribute("currentPage", "login"); model.addAttribute("currentPage", "login");
if (request.getParameter("error") != null) { if (request.getParameter("error") != null) {
@ -85,14 +92,29 @@ public class AccountWebController {
} }
if (authentication != null && authentication.isAuthenticated()) { if (authentication != null && authentication.isAuthenticated()) {
Object principal = authentication.getPrincipal(); Object principal = authentication.getPrincipal();
String username = null;
if (principal instanceof UserDetails) { if (principal instanceof UserDetails) {
// Cast the principal object to UserDetails // Cast the principal object to UserDetails
UserDetails userDetails = (UserDetails) principal; UserDetails userDetails = (UserDetails) principal;
// Retrieve username and other attributes // 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 // Fetch user details from the database
Optional<User> user = Optional<User> user =
userRepository.findByUsernameIgnoreCase( userRepository.findByUsernameIgnoreCase(

View File

@ -17,6 +17,7 @@ import io.swagger.v3.oas.annotations.tags.Tag;
@Controller @Controller
@Tag(name = "Misc", description = "Miscellaneous APIs") @Tag(name = "Misc", description = "Miscellaneous APIs")
public class OtherWebController { public class OtherWebController {
@GetMapping("/compress-pdf") @GetMapping("/compress-pdf")
@Hidden @Hidden
public String compressPdfForm(Model model) { public String compressPdfForm(Model model) {
@ -53,6 +54,13 @@ public class OtherWebController {
return "misc/add-page-numbers"; 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") @GetMapping("/extract-images")
@Hidden @Hidden
public String extractImagesForm(Model model) { public String extractImagesForm(Model model) {
@ -81,6 +89,13 @@ public class OtherWebController {
return "misc/compare"; 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() { public List<String> getAvailableTesseractLanguages() {
String tessdataDir = "/usr/share/tessdata"; String tessdataDir = "/usr/share/tessdata";
File[] files = new File(tessdataDir).listFiles(); File[] files = new File(tessdataDir).listFiles();

View File

@ -118,6 +118,7 @@ public class ApplicationProperties {
private Boolean enableLogin; private Boolean enableLogin;
private Boolean csrfDisabled; private Boolean csrfDisabled;
private InitialLogin initialLogin; private InitialLogin initialLogin;
private OAUTH2 oauth2;
private int loginAttemptCount; private int loginAttemptCount;
private long loginResetTimeMinutes; private long loginResetTimeMinutes;
@ -145,6 +146,14 @@ public class ApplicationProperties {
this.initialLogin = initialLogin; this.initialLogin = initialLogin;
} }
public OAUTH2 getOAUTH2() {
return oauth2 != null ? oauth2 : new OAUTH2();
}
public void setOAUTH2(OAUTH2 oauth2) {
this.oauth2 = oauth2;
}
public Boolean getEnableLogin() { public Boolean getEnableLogin() {
return enableLogin; return enableLogin;
} }
@ -165,6 +174,8 @@ public class ApplicationProperties {
public String toString() { public String toString() {
return "Security [enableLogin=" return "Security [enableLogin="
+ enableLogin + enableLogin
+ ", oauth2="
+ oauth2
+ ", initialLogin=" + ", initialLogin="
+ initialLogin + initialLogin
+ ", csrfDisabled=" + ", 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 { public static class System {
@ -210,6 +285,33 @@ public class ApplicationProperties {
private String rootURIPath; private String rootURIPath;
private String customStaticFilePath; private String customStaticFilePath;
private Integer maxFileSize; 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; private Boolean enableAlphaFunctionality;
@ -275,6 +377,10 @@ public class ApplicationProperties {
+ maxFileSize + maxFileSize
+ ", enableAlphaFunctionality=" + ", enableAlphaFunctionality="
+ enableAlphaFunctionality + enableAlphaFunctionality
+ ", showUpdate="
+ showUpdate
+ ", showUpdateOnlyAdmin="
+ showUpdateOnlyAdmin
+ "]"; + "]";
} }
} }

View File

@ -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;
}

View File

@ -0,0 +1,17 @@
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 FlattenRequest extends PDFFile {
@Schema(
description =
"True to flatten only the forms, false to flatten full PDF (Convert page to image)")
private Boolean flattenOnlyForms;
}

View File

@ -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;
}

View File

@ -9,4 +9,6 @@ import stirling.software.SPDF.model.Authority;
public interface AuthorityRepository extends JpaRepository<Authority, Long> { public interface AuthorityRepository extends JpaRepository<Authority, Long> {
// Set<Authority> findByUsername(String username); // Set<Authority> findByUsername(String username);
Set<Authority> findByUser_Username(String username); Set<Authority> findByUser_Username(String username);
Authority findByUserId(long user_id);
} }

View File

@ -336,14 +336,12 @@ public class PdfUtils {
} }
} }
private static void addImageToDocument( public static void addImageToDocument(
PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate) PDDocument doc, PDImageXObject image, String fitOption, boolean autoRotate)
throws IOException { throws IOException {
boolean imageIsLandscape = image.getWidth() > image.getHeight(); boolean imageIsLandscape = image.getWidth() > image.getHeight();
PDRectangle pageSize = PDRectangle.A4; PDRectangle pageSize = PDRectangle.A4;
System.out.println(fitOption);
if (autoRotate && imageIsLandscape) { if (autoRotate && imageIsLandscape) {
pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth()); pageSize = new PDRectangle(pageSize.getHeight(), pageSize.getWidth());
} }

View File

@ -58,10 +58,10 @@ public class ProcessExecutor {
long timeoutMinutes = long timeoutMinutes =
switch (key) { switch (key) {
case LIBRE_OFFICE -> 30; case LIBRE_OFFICE -> 30;
case PDFTOHTML -> 5; case PDFTOHTML -> 20;
case OCR_MY_PDF -> 30; case OCR_MY_PDF -> 30;
case PYTHON_OPENCV -> 30; case PYTHON_OPENCV -> 30;
case GHOSTSCRIPT -> 5; case GHOSTSCRIPT -> 30;
case WEASYPRINT -> 30; case WEASYPRINT -> 30;
case INSTALL_APP -> 60; case INSTALL_APP -> 60;
case CALIBRE -> 30; case CALIBRE -> 30;

View File

@ -24,8 +24,8 @@ spring.devtools.livereload.enabled=true
spring.thymeleaf.encoding=UTF-8 spring.thymeleaf.encoding=UTF-8
server.connection-timeout=${SYSTEM_CONNECTIONTIMEOUTMINUTES:5m} server.connection-timeout=${SYSTEM_CONNECTIONTIMEOUTMINUTES:20m}
spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:300000} spring.mvc.async.request-timeout=${SYSTEM_CONNECTIONTIMEOUTMILLISECONDS:1200000}
spring.resources.static-locations=file:customFiles/static/ spring.resources.static-locations=file:customFiles/static/
#spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/ #spring.thymeleaf.prefix=file:/customFiles/templates/,classpath:/templates/

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=لا يمكن خفض دور المستخدم الحالي
downgradeCurrentUserLongMessage=لا يمكن تخفيض دور المستخدم الحالي. وبالتالي، لن يظهر المستخدم الحالي.
error=Error error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=إعدادات
############# #############
settings.title=الإعدادات settings.title=الإعدادات
settings.update=التحديث متاح settings.update=التحديث متاح
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=إصدار التطبيق: settings.appVersion=إصدار التطبيق:
settings.downloadOption.title=تحديد خيار التنزيل (للتنزيلات ذات الملف الواحد غير المضغوط): settings.downloadOption.title=تحديد خيار التنزيل (للتنزيلات ذات الملف الواحد غير المضغوط):
settings.downloadOption.1=فتح في نفس النافذة settings.downloadOption.1=فتح في نفس النافذة
@ -120,8 +123,9 @@ settings.downloadOption.3=تنزيل الملف
settings.zipThreshold=ملفات مضغوطة عند تجاوز عدد الملفات التي تم تنزيلها settings.zipThreshold=ملفات مضغوطة عند تجاوز عدد الملفات التي تم تنزيلها
settings.signOut=Sign Out settings.signOut=Sign Out
settings.accountSettings=Account Settings 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=تغيير دور المستخدم
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Remember me
login.invalid=Invalid username or password. login.invalid=Invalid username or password.
login.locked=Your account has been locked. login.locked=Your account has been locked.
login.signinTitle=Please sign in login.signinTitle=Please sign in
login.ssoSignIn=تسجيل الدخول عبر تسجيل الدخول الأحادي
login.oauth2AutoCreateDisabled=تم تعطيل مستخدم الإنشاء التلقائي لـ OAuth2
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=الإصلاح
#flatten #flatten
flatten.title=تسطيح flatten.title=تسطيح
flatten.header=تسوية ملفات PDF flatten.header=تسوية ملفات PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=تسطيح flatten.submit=تسطيح
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF إلى PDF / A
pdfToPDFA.credit=تستخدم هذه الخدمة OCRmyPDF لتحويل PDF / A. pdfToPDFA.credit=تستخدم هذه الخدمة OCRmyPDF لتحويل PDF / A.
pdfToPDFA.submit=تحويل pdfToPDFA.submit=تحويل
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -11,17 +11,17 @@ imgPrompt=Изберете изображение(я)
genericSubmit=Подайте genericSubmit=Подайте
processTimeWarning=Предупреждение: Този процес може да отнеме до минута в зависимост от размера на файла processTimeWarning=Предупреждение: Този процес може да отнеме до минута в зависимост от размера на файла
pageOrderPrompt=Персонализиран ред на страниците (Въведете разделен със запетаи списък с номера на страници или функции като 2n+1): pageOrderPrompt=Персонализиран ред на страниците (Въведете разделен със запетаи списък с номера на страници или функции като 2n+1):
pageSelectionPrompt=Custom Page Selection (Enter a comma-separated list of page numbers 1,5,6 or Functions like 2n+1) : pageSelectionPrompt=Персонализиран избор на страница (Въведете списък с номера на страници 1,5,6, разделени със запетая, или функции като 2n+1) :
goToPage=Давай goToPage=Давай
true=Вярно true=Вярно
false=Невярно false=Невярно
unknown=Непознат unknown=Непознат
save=Съхранете save=Съхранете
saveToBrowser=Save to Browser saveToBrowser=Съхраняване в браузъра
close=Затворете close=Затворете
filesSelected=избрани файлове filesSelected=избрани файлове
noFavourites=Няма добавени любими noFavourites=Няма добавени любими
downloadComplete=Download Complete downloadComplete=Свалянето завършено
bored=Отекчени сте да чакате? bored=Отекчени сте да чакате?
alphabet=Азбука alphabet=Азбука
downloadPdf=Изтеглете PDF downloadPdf=Изтеглете PDF
@ -45,54 +45,56 @@ red=Червено
green=Зелено green=Зелено
blue=Синьо blue=Синьо
custom=Персонализиране... custom=Персонализиране...
WorkInProgess=Work in progress, May not work or be buggy, Please report any problems! WorkInProgess=Работата е в ход, може да не работи или да има грешки, моля, докладвайте за проблеми!
poweredBy=Powered by poweredBy=Задвижван чрез
yes=Yes yes=Да
no=No no=Не
changedCredsMessage=Идентификационните данни са променени! changedCredsMessage=Идентификационните данни са променени!
notAuthenticatedMessage=Потребителят не е автентикиран. notAuthenticatedMessage=Потребителят не е автентикиран.
userNotFoundMessage=Потребителят не е намерен userNotFoundMessage=Потребителят не е намерен
incorrectPasswordMessage=Текущата парола е неправилна. incorrectPasswordMessage=Текущата парола е неправилна.
usernameExistsMessage=Новият потребител вече съществува. usernameExistsMessage=Новият потребител вече съществува.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Невалидно потребителско име, потребителското име трябва да съдържа само букви и цифри.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Не може да се изтрие вписания в момента потребител.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
error=Error downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
oops=Oops! downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
help=Help error=Грешка
goHomepage=Go to Homepage oops=Опаа!
joinDiscord=Join our Discord server help=Помощ
seeDockerHub=See Docker Hub goHomepage=Отидете на началната страница
visitGithub=Visit Github Repository joinDiscord=Присъединете се към нашия Discord сървър
donate=Donate seeDockerHub=Погледнете Docker Hub
color=Color visitGithub=Посетете Github Repository
sponsor=Sponsor donate=Направете дарение
color=Цвят
sponsor=Спонсор
############### ###############
# Pipeline # # Pipeline #
############### ###############
pipeline.header=Pipeline Menu (Beta) pipeline.header=Pipeline Меню (Бета)
pipeline.uploadButton=Upload Custom pipeline.uploadButton=Качване на персонализиран
pipeline.configureButton=Configure pipeline.configureButton=Настройка
pipeline.defaultOption=Custom pipeline.defaultOption=Персонализиран
pipeline.submitButton=Submit pipeline.submitButton=Подайте
pipeline.help=Pipeline Help pipeline.help=Pipeline Помощ
pipeline.scanHelp=Folder Scanning Help pipeline.scanHelp=Помощ за сканиране на папки
###################### ######################
# Pipeline Options # # Pipeline Options #
###################### ######################
pipelineOptions.header=Pipeline Configuration pipelineOptions.header=Pipeline Конфигурация
pipelineOptions.pipelineNameLabel=Pipeline Name pipelineOptions.pipelineNameLabel=Pipeline име
pipelineOptions.saveSettings=Save Operation Settings pipelineOptions.saveSettings=Запазете настройките за работа
pipelineOptions.pipelineNamePrompt=Enter pipeline name here pipelineOptions.pipelineNamePrompt=Въведете името на pipeline тук
pipelineOptions.selectOperation=Select Operation pipelineOptions.selectOperation=Избор на операция
pipelineOptions.addOperationButton=Add operation pipelineOptions.addOperationButton=Добавяне на операция
pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.pipelineHeader=Pipeline:
pipelineOptions.saveButton=Download pipelineOptions.saveButton=Изтегли
pipelineOptions.validateButton=Validate pipelineOptions.validateButton=Валидирай
@ -112,6 +114,7 @@ navbar.settings=Настройки
############# #############
settings.title=Настройки settings.title=Настройки
settings.update=Налична актуализация settings.update=Налична актуализация
settings.updateAvailable={0} е текущата инсталирана версия. Налична е нова версия ({1}).
settings.appVersion=Версия на приложението: settings.appVersion=Версия на приложението:
settings.downloadOption.title=Изберете опция за изтегляне (за изтегляния на един файл без да е архивиран): settings.downloadOption.title=Изберете опция за изтегляне (за изтегляния на един файл без да е архивиран):
settings.downloadOption.1=Отваряне в същия прозорец settings.downloadOption.1=Отваряне в същия прозорец
@ -120,12 +123,13 @@ settings.downloadOption.3=Изтегли файл
settings.zipThreshold=Архивирайте файловете, когато броят на изтеглените файлове надвишава settings.zipThreshold=Архивирайте файловете, когато броят на изтеглените файлове надвишава
settings.signOut=Изход settings.signOut=Изход
settings.accountSettings=Настройки на акаунта settings.accountSettings=Настройки на акаунта
settings.bored.help=Активира игра с великденски яйца
settings.cacheInputs.name=Запазете въведените формуляри
settings.cacheInputs.help=Активирайте за съхраняване на предишни използвани въведени данни за бъдещи изпълнения
changeCreds.title=Промяна на идентификационните данни changeCreds.title=Промяна на идентификационните данни
changeCreds.header=Актуализирайте данните за акаунта си changeCreds.header=Актуализирайте данните за акаунта си
changeCreds.changePassword=You are using default login credentials. Please enter a new password changeCreds.changePassword=Използвате идентификационни данни за вход по подразбиране. Моля, въведете нова парола
changeCreds.newUsername=Ново потребителско име changeCreds.newUsername=Ново потребителско име
changeCreds.oldPassword=Текуща парола changeCreds.oldPassword=Текуща парола
changeCreds.newPassword=Нова парола changeCreds.newPassword=Нова парола
@ -151,8 +155,8 @@ account.syncTitle=Синхронизиране на настройките на
account.settingsCompare=Сравняване на настройките: account.settingsCompare=Сравняване на настройките:
account.property=Свойство account.property=Свойство
account.webBrowserSettings=Уеб-браузър настройки account.webBrowserSettings=Уеб-браузър настройки
account.syncToBrowser=Синхронизиране на акаунт -> Бразър account.syncToBrowser=Синхронизиране на акаунт -> Браузър
account.syncToAccount=Синхронизиране на акаунт <- Бразър account.syncToAccount=Синхронизиране на акаунт <- Браузър
adminUserSettings.title=Настройки за потребителски контрол adminUserSettings.title=Настройки за потребителски контрол
@ -160,28 +164,29 @@ adminUserSettings.header=Настройки за администраторск
adminUserSettings.admin=Администратор adminUserSettings.admin=Администратор
adminUserSettings.user=Потребител adminUserSettings.user=Потребител
adminUserSettings.addUser=Добавяне на нов потребител adminUserSettings.addUser=Добавяне на нов потребител
adminUserSettings.usernameInfo=Username must only contain letters and numbers, no spaces or special characters. adminUserSettings.usernameInfo=Потребителското име трябва да съдържа само букви и цифри, без интервали или специални знаци.
adminUserSettings.roles=Роли adminUserSettings.roles=Роли
adminUserSettings.role=Роля adminUserSettings.role=Роля
adminUserSettings.actions=Действия adminUserSettings.actions=Действия
adminUserSettings.apiUser=Ограничен API потребител adminUserSettings.apiUser=Ограничен API потребител
adminUserSettings.extraApiUser=Additional Limited API User adminUserSettings.extraApiUser=Допълнителен ограничен API потребител
adminUserSettings.webOnlyUser=Само за уеб-потребител adminUserSettings.webOnlyUser=Само за уеб-потребител
adminUserSettings.demoUser=Demo User (No custom settings) adminUserSettings.demoUser=Демо потребител (без персонализирани настройки)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Вътрешен API потребител
adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане adminUserSettings.forceChange=Принудете потребителя да промени потребителското име/парола при влизане
adminUserSettings.submit=Съхранете потребителя adminUserSettings.submit=Съхранете потребителя
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
############# #############
# HOME-PAGE # # HOME-PAGE #
############# #############
home.desc=Вашето локално хоствано обслужване на едно място за всички ваши PDF нужди. home.desc=Вашето локално хоствано обслужване на едно място за всички ваши PDF нужди.
home.searchBar=Search for features... home.searchBar=Търсене на функции...
home.viewPdf.title=View PDF home.viewPdf.title=Преглед на PDF
home.viewPdf.desc=View, annotate, add text or images home.viewPdf.desc=Преглеждайте, коментирайте, добавяйте текст или изображения
viewPdf.tags=view,read,annotate,text,image viewPdf.tags=преглед,четене,анотиране,текст,изображение
home.multiTool.title=PDF Мулти инструмент home.multiTool.title=PDF Мулти инструмент
home.multiTool.desc=Обединяване, завъртане, пренареждане и премахване на страници home.multiTool.desc=Обединяване, завъртане, пренареждане и премахване на страници
@ -252,7 +257,7 @@ home.fileToPDF.desc=Преобразуване почти всеки файл к
fileToPDF.tags=трансформация,формат,документ,изображение,слайд,текст,преобразуване,офис,документи,word,excel,powerpoint fileToPDF.tags=трансформация,формат,документ,изображение,слайд,текст,преобразуване,офис,документи,word,excel,powerpoint
home.ocr.title=OCR / Почистващи сканирания home.ocr.title=OCR / Почистващи сканирания
home.ocr.desc=Cleanup сканира и открива текст от изображения към PDF и го добавя отново като текст. home.ocr.desc=Почистване, сканира и открива текст от изображения към PDF и го добавя отново като текст.
ocr.tags=разпознаване,текст,изображение,сканиране,четене,идентифициране,откриване,редактиране ocr.tags=разпознаване,текст,изображение,сканиране,четене,идентифициране,откриване,редактиране
@ -305,9 +310,9 @@ home.removeBlanks.title=Премахване на празни страници
home.removeBlanks.desc=Открива и премахва празни страници от документ home.removeBlanks.desc=Открива и премахва празни страници от документ
removeBlanks.tags=почистване,рационализиране,без съдържание,организиране removeBlanks.tags=почистване,рационализиране,без съдържание,организиране
home.removeAnnotations.title=Remove Annotations home.removeAnnotations.title=Премахване на анотации
home.removeAnnotations.desc=Removes all comments/annotations from a PDF home.removeAnnotations.desc=Премахва всички коментари/анотации от PDF
removeAnnotations.tags=comments,highlight,notes,markup,remove removeAnnotations.tags=коментари, маркиране, бележки, маркиране, премахване
home.compare.title=Сравнете home.compare.title=Сравнете
home.compare.desc=Сравнява и показва разликите между 2 PDF документа home.compare.desc=Сравнява и показва разликите между 2 PDF документа
@ -349,7 +354,7 @@ home.autoSplitPDF.title=Автоматично разделяне на стра
home.autoSplitPDF.desc=Автоматично разделяне на сканиран PDF файл с QR код за разделяне на физически сканирани страници home.autoSplitPDF.desc=Автоматично разделяне на сканиран PDF файл с QR код за разделяне на физически сканирани страници
autoSplitPDF.tags=QR-базиран,отделен,сканиране-сегмент,организиране autoSplitPDF.tags=QR-базиран,отделен,сканиране-сегмент,организиране
home.sanitizePdf.title=Дезинфекцирай home.sanitizePdf.title=Дезинфенкцирам
home.sanitizePdf.desc=Премахване на скриптове и други елементи от PDF файлове home.sanitizePdf.desc=Премахване на скриптове и други елементи от PDF файлове
sanitizePdf.tags=чисти,сигурни,безопасни,премахване-заплахи sanitizePdf.tags=чисти,сигурни,безопасни,премахване-заплахи
@ -391,35 +396,35 @@ home.autoRedact.desc=Автоматично редактира (зачерняв
autoRedact.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит autoRedact.tags=Редактиране,Скриване,затъмняване,черен,маркер,скрит
home.tableExtraxt.title=PDF to CSV home.tableExtraxt.title=PDF to CSV
home.tableExtraxt.desc=Extracts Tables from a PDF converting it to CSV home.tableExtraxt.desc=Извлича таблици от PDF, като ги конвертира в CSV
tableExtraxt.tags=CSV,Table Extraction,extract,convert tableExtraxt.tags=CSV,извличане на таблица,извличане,конвертиране
home.autoSizeSplitPDF.title=Auto Split by Size/Count home.autoSizeSplitPDF.title=Автоматично разделяне по размер/брой
home.autoSizeSplitPDF.desc=Split a single PDF into multiple documents based on size, page count, or document count home.autoSizeSplitPDF.desc=Разделете един PDF на множество документи въз основа на размер, брой страници или брой документи
autoSizeSplitPDF.tags=pdf,split,document,organization autoSizeSplitPDF.tags=pdf,разделяне,документ,организация
home.overlay-pdfs.title=Overlay PDFs home.overlay-pdfs.title=Наслагване PDF-и
home.overlay-pdfs.desc=Overlays PDFs on-top of another PDF home.overlay-pdfs.desc=Наслагва PDF файлове върху друг PDF
overlay-pdfs.tags=Overlay overlay-pdfs.tags=Наслагване
home.split-by-sections.title=Split PDF by Sections home.split-by-sections.title=Разделяне на PDF по секции
home.split-by-sections.desc=Divide each page of a PDF into smaller horizontal and vertical sections home.split-by-sections.desc=Разделете всяка страница от PDF на по-малки хоризонтални и вертикални секции
split-by-sections.tags=Section Split, Divide, Customize split-by-sections.tags=Разделяне на секция,Разделяне,Персонализиране
home.AddStampRequest.title=Add Stamp to PDF home.AddStampRequest.title=Добавяне на печат към PDF
home.AddStampRequest.desc=Add text or add image stamps at set locations home.AddStampRequest.desc=Добавете текст или добавете печати с изображения на определени места
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize AddStampRequest.tags=Печат,добавяне на изображение,централно изображение,воден знак,PDF,вграждане,персонализиране
home.PDFToBook.title=PDF to Book home.PDFToBook.title=PDF към книга
home.PDFToBook.desc=Converts PDF to Book/Comic formats using calibre home.PDFToBook.desc=Преобразува PDF във формати на книги/комикси с помощта на calibre
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle PDFToBook.tags=Книга,комикс,calibre,конвертиране,манга,Amazon,Kindle
home.BookToPDF.title=Book to PDF home.BookToPDF.title=Книга към PDF
home.BookToPDF.desc=Converts Books/Comics formats to PDF using calibre home.BookToPDF.desc=Преобразува формати на книги/комикси в PDF с помощта на calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle BookToPDF.tags=Книга,комикс,calibre,конвертиране,манга,Amazon,Kindle
########################### ###########################
@ -435,6 +440,8 @@ login.rememberme=Запомни ме
login.invalid=Невалидно потребителско име или парола. login.invalid=Невалидно потребителско име или парола.
login.locked=Вашият акаунт е заключен. login.locked=Вашият акаунт е заключен.
login.signinTitle=Моля впишете се login.signinTitle=Моля впишете се
login.ssoSignIn=Влизане чрез еднократно влизане
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
#auto-redact #auto-redact
@ -464,9 +471,9 @@ pdfToSinglePage.submit=Преобразуване към единична стр
#pageExtracter #pageExtracter
pageExtracter.title=Extract Pages pageExtracter.title=Извличане на страници
pageExtracter.header=Extract Pages pageExtracter.header=Извличане на страници
pageExtracter.submit=Extract pageExtracter.submit=Извличане
pageExtracter.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1) pageExtracter.placeholder=(e.g. 1,2,8 or 4,7,12-16 or 2n-1)
@ -496,40 +503,40 @@ URLToPDF.credit=Използва WeasyPrint
#html-to-pdf #html-to-pdf
HTMLToPDF.title=HTML към PDF HTMLToPDF.title=HTML към PDF
HTMLToPDF.header=HTML към PDF HTMLToPDF.header=HTML към PDF
HTMLToPDF.help=Приема HTML файлове и ZIP файлове, съдържащи html/css/изображения и т.н HTMLToPDF.help=Приемане на HTML файлове и ZIP файлове, съдържащи html/css/изображения и т.н.
HTMLToPDF.submit=Преобразуване HTMLToPDF.submit=Преобразуване
HTMLToPDF.credit=Използва WeasyPrint HTMLToPDF.credit=Използва WeasyPrint
HTMLToPDF.zoom=Zoom level for displaying the website. HTMLToPDF.zoom=Ниво на мащабиране за показване на уебсайта.
HTMLToPDF.pageWidth=Width of the page in centimeters. (Blank to default) HTMLToPDF.pageWidth=Ширина на страницата в сантиметри. (Празно по подразбиране)
HTMLToPDF.pageHeight=Height of the page in centimeters. (Blank to default) HTMLToPDF.pageHeight=Височина на страницата в сантиметри. (Празно по подразбиране)
HTMLToPDF.marginTop=Top margin of the page in millimeters. (Blank to default) HTMLToPDF.marginTop=Горно поле на страницата в милиметри. (Празно по подразбиране)
HTMLToPDF.marginBottom=Bottom margin of the page in millimeters. (Blank to default) HTMLToPDF.marginBottom=Долно поле на страницата в милиметри. (Празно по подразбиране)
HTMLToPDF.marginLeft=Left margin of the page in millimeters. (Blank to default) HTMLToPDF.marginLeft=Ляво поле на страницата в милиметри. (Празно по подразбиране)
HTMLToPDF.marginRight=Right margin of the page in millimeters. (Blank to default) HTMLToPDF.marginRight=Дясно поле на страницата в милиметри. (Празно по подразбиране)
HTMLToPDF.printBackground=Render the background of websites. HTMLToPDF.printBackground=Изобразете фона на уебсайтове.
HTMLToPDF.defaultHeader=Enable Default Header (Name and page number) HTMLToPDF.defaultHeader=Активиране на горния колонтитул по подразбиране (име и номер на страница)
HTMLToPDF.cssMediaType=Change the CSS media type of the page. HTMLToPDF.cssMediaType=Променете CSS медийния тип на страницата.
HTMLToPDF.none=None HTMLToPDF.none=Няма
HTMLToPDF.print=Print HTMLToPDF.print=Печат
HTMLToPDF.screen=Screen HTMLToPDF.screen=Екран
#AddStampRequest #AddStampRequest
AddStampRequest.header=Stamp PDF AddStampRequest.header=Поставяне на печат на PDF
AddStampRequest.title=Stamp PDF AddStampRequest.title=Поставяне на печат на PDF
AddStampRequest.stampType=Stamp Type AddStampRequest.stampType=Тип печат
AddStampRequest.stampText=Stamp Text AddStampRequest.stampText=Поставяне на текст
AddStampRequest.stampImage=Stamp Image AddStampRequest.stampImage=Изображение с печат
AddStampRequest.alphabet=Alphabet AddStampRequest.alphabet=Азбука
AddStampRequest.fontSize=Font/Image Size AddStampRequest.fontSize=Размер на шрифта/изображението
AddStampRequest.rotation=Rotation AddStampRequest.rotation=Ротация
AddStampRequest.opacity=Opacity AddStampRequest.opacity=Непрозрачност
AddStampRequest.position=Position AddStampRequest.position=Позиция
AddStampRequest.overrideX=Override X Coordinate AddStampRequest.overrideX=Замяна на X координата
AddStampRequest.overrideY=Override Y Coordinate AddStampRequest.overrideY=Замяна на Y координата
AddStampRequest.customMargin=Custom Margin AddStampRequest.customMargin=Персонализиран марж
AddStampRequest.customColor=Custom Text Color AddStampRequest.customColor=Персонализиран цвят на текста
AddStampRequest.submit=Submit AddStampRequest.submit=Изпращане
#sanitizePDF #sanitizePDF
@ -618,11 +625,11 @@ scalePages.submit=Подайте
certSign.title=Подписване на сертификат certSign.title=Подписване на сертификат
certSign.header=Подпишете PDF с вашия сертификат (В процес на работа) certSign.header=Подпишете PDF с вашия сертификат (В процес на работа)
certSign.selectPDF=Изберете 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.selectKey=Изберете вашия файл с личен ключ (формат PKCS#8, може да бъде .pem или .der):
certSign.selectCert=Изберете вашия файл със сертификат (формат X.509, може да бъде .pem или .der): certSign.selectCert=Изберете вашия файл със сертификат (формат X.509, може да бъде .pem или .der):
certSign.selectP12=Изберете вашия PKCS#12 Keystore файл (.p12 или .pfx) (По избор, ако е предоставен, трябва да съдържа вашия личен ключ и сертификат): 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.certType=Тип сертификат
certSign.password=Въведете вашата парола за Keystore за ключове или частен ключ (ако има): certSign.password=Въведете вашата парола за Keystore за ключове или частен ключ (ако има):
certSign.showSig=Показване на подпис certSign.showSig=Показване на подпис
@ -643,9 +650,9 @@ removeBlanks.submit=Премахване на празни места
#removeAnnotations #removeAnnotations
removeAnnotations.title=Remove Annotations removeAnnotations.title=Премахване на анотации
removeAnnotations.header=Remove Annotations removeAnnotations.header=Премахване на анотации
removeAnnotations.submit=Remove removeAnnotations.submit=Премахване
#compare #compare
@ -656,17 +663,17 @@ compare.document.2=Документ 2
compare.submit=Сравнявай compare.submit=Сравнявай
#BookToPDF #BookToPDF
BookToPDF.title=Books and Comics to PDF BookToPDF.title=Книги и комикси в PDF
BookToPDF.header=Book to PDF BookToPDF.header=Книга в PDF
BookToPDF.credit=Uses Calibre BookToPDF.credit=Използва Calibre
BookToPDF.submit=Convert BookToPDF.submit=Конвертиране
#PDFToBook #PDFToBook
PDFToBook.title=PDF to Book PDFToBook.title=PDF към книга
PDFToBook.header=PDF to Book PDFToBook.header=PDF към книга
PDFToBook.selectText.1=Format PDFToBook.selectText.1=Формат
PDFToBook.credit=Uses Calibre PDFToBook.credit=Използва Calibre
PDFToBook.submit=Convert PDFToBook.submit=Конвертиране
#sign #sign
sign.title=Подпишете sign.title=Подпишете
@ -687,6 +694,7 @@ repair.submit=Поправи
#flatten #flatten
flatten.title=Изравнете flatten.title=Изравнете
flatten.header=Изравнете PDF-и flatten.header=Изравнете PDF-и
flatten.flattenOnlyForms=Изравнете само форми
flatten.submit=Изравнете flatten.submit=Изравнете
@ -746,7 +754,7 @@ compress.selectText.1=Ръчен режим - От 1 до 4
compress.selectText.2=Ниво на оптимизация: compress.selectText.2=Ниво на оптимизация:
compress.selectText.3=4 (Ужасно за текстови изображения) compress.selectText.3=4 (Ужасно за текстови изображения)
compress.selectText.4=Автоматичен режим - Автоматично настройва качеството, за да получи PDF точен размер compress.selectText.4=Автоматичен режим - Автоматично настройва качеството, за да получи PDF точен размер
compress.selectText.5=Очакван PDF размер (напр. 25MB, 10.8MB, 25KB) compress.selectText.5=Очакван PDF размер (напр. 25МБ, 10.8МБ, 25КБ)
compress.submit=Компресиране compress.submit=Компресиране
@ -770,27 +778,27 @@ merge.submit=Обединяване
pdfOrganiser.title=Организатор на страници pdfOrganiser.title=Организатор на страници
pdfOrganiser.header=Организатор на PDF страници pdfOrganiser.header=Организатор на PDF страници
pdfOrganiser.submit=Пренареждане на страниците pdfOrganiser.submit=Пренареждане на страниците
pdfOrganiser.mode=Mode pdfOrganiser.mode=Режим
pdfOrganiser.mode.1=Custom Page Order pdfOrganiser.mode.1=Персонализиран ред на страниците
pdfOrganiser.mode.2=Reverse Order pdfOrganiser.mode.2=Обърнат ред
pdfOrganiser.mode.3=Duplex Sort pdfOrganiser.mode.3=Двустранно сортиране
pdfOrganiser.mode.4=Booklet Sort pdfOrganiser.mode.4=Сортиране на брошури
pdfOrganiser.mode.5=Side Stitch Booklet Sort pdfOrganiser.mode.5=Сортиране на брошури със страничен шев
pdfOrganiser.mode.6=Odd-Even Split pdfOrganiser.mode.6=Четно-нечетно разделяне
pdfOrganiser.mode.7=Remove First pdfOrganiser.mode.7=Премахни първо
pdfOrganiser.mode.8=Remove Last pdfOrganiser.mode.8=Премахване на последния
pdfOrganiser.mode.9=Remove First and Last pdfOrganiser.mode.9=Премахване на първия и последния
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1) pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
#multiTool #multiTool
multiTool.title=PDF Мулти инструмент multiTool.title=PDF Мулти инструмент
multiTool.header=PDF Мулти инструмент multiTool.header=PDF Мулти инструмент
multiTool.uploadPrompts=Please Upload PDF multiTool.uploadPrompts=Моля качете PDF
#view pdf #view pdf
viewPdf.title=View PDF viewPdf.title=Преглед на PDF
viewPdf.header=View PDF viewPdf.header=Преглед на PDF
#pageRemover #pageRemover
pageRemover.title=Премахване на страници pageRemover.title=Премахване на страници
@ -938,7 +946,8 @@ pdfToPDFA.title=PDF към PDF/A
pdfToPDFA.header=PDF към PDF/A pdfToPDFA.header=PDF към PDF/A
pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A преобразуване. pdfToPDFA.credit=Тази услуга използва OCRmyPDF за PDF/A преобразуване.
pdfToPDFA.submit=Преобразуване pdfToPDFA.submit=Преобразуване
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
pdfToPDFA.outputFormat=Изходен формат
#PDFToWord #PDFToWord
@ -981,66 +990,75 @@ PDFToXML.submit=Преобразуване
#PDFToCSV #PDFToCSV
PDFToCSV.title=PDF ??? CSV PDFToCSV.title=PDF ??? CSV
PDFToCSV.header=PDF ??? CSV PDFToCSV.header=PDF ??? CSV
PDFToCSV.prompt=Choose page to extract table PDFToCSV.prompt=Изберете страница за извличане на таблица
PDFToCSV.submit=???????? PDFToCSV.submit=????
#split-by-size-or-count #split-by-size-or-count
split-by-size-or-count.title=Split PDF by Size or Count split-by-size-or-count.title=Разделяне на PDF по размер или брой
split-by-size-or-count.header=Split PDF by Size or Count split-by-size-or-count.header=Разделяне на PDF по размер или брой
split-by-size-or-count.type.label=Select Split Type split-by-size-or-count.type.label=Изберете тип разделяне
split-by-size-or-count.type.size=By Size split-by-size-or-count.type.size=По размер
split-by-size-or-count.type.pageCount=By Page Count split-by-size-or-count.type.pageCount=По брой страници
split-by-size-or-count.type.docCount=By Document Count split-by-size-or-count.type.docCount=По брой документи
split-by-size-or-count.value.label=Enter Value split-by-size-or-count.value.label=Въведете стойност
split-by-size-or-count.value.placeholder=Enter size (e.g., 2MB or 3KB) or count (e.g., 5) split-by-size-or-count.value.placeholder=Въведете размер (напр. 2МБ или 3КБ) или брой (напр. 5)
split-by-size-or-count.submit=Submit split-by-size-or-count.submit=Изпращане
#overlay-pdfs #overlay-pdfs
overlay-pdfs.header=Overlay PDF Files overlay-pdfs.header=Наслагване на PDF файлове
overlay-pdfs.baseFile.label=Select Base PDF File overlay-pdfs.baseFile.label=Изберете Основен PDF файл
overlay-pdfs.overlayFiles.label=Select Overlay PDF Files overlay-pdfs.overlayFiles.label=Изберете наслагване на PDF файлове
overlay-pdfs.mode.label=Select Overlay Mode overlay-pdfs.mode.label=Изберете режим на наслагване
overlay-pdfs.mode.sequential=Sequential Overlay overlay-pdfs.mode.sequential=Последователно наслагване
overlay-pdfs.mode.interleaved=Interleaved Overlay overlay-pdfs.mode.interleaved=Преплетено наслагване
overlay-pdfs.mode.fixedRepeat=Fixed Repeat Overlay overlay-pdfs.mode.fixedRepeat=Фиксирано наслагване при повторение
overlay-pdfs.counts.label=Overlay Counts (for Fixed Repeat Mode) overlay-pdfs.counts.label=Брой наслагвания (за режим на фиксирано повторение)
overlay-pdfs.counts.placeholder=Enter comma-separated counts (e.g., 2,3,1) overlay-pdfs.counts.placeholder=Въведете броя, разделени със запетая (напр. 2,3,1)
overlay-pdfs.position.label=Select Overlay Position overlay-pdfs.position.label=Изберете позиция на наслагване
overlay-pdfs.position.foreground=Foreground overlay-pdfs.position.foreground=Преден план
overlay-pdfs.position.background=Background overlay-pdfs.position.background=Фон
overlay-pdfs.submit=Submit overlay-pdfs.submit=Изпращане
#split-by-sections #split-by-sections
split-by-sections.title=Split PDF by Sections split-by-sections.title=Разделяне на PDF по секции
split-by-sections.header=Split PDF into Sections split-by-sections.header=Разделяне на PDF на секции
split-by-sections.horizontal.label=Horizontal Divisions split-by-sections.horizontal.label=Хоризонтални разделения
split-by-sections.vertical.label=Vertical Divisions split-by-sections.vertical.label=Вертикални разделения
split-by-sections.horizontal.placeholder=Enter number of horizontal divisions split-by-sections.horizontal.placeholder=Въведете брой хоризонтални деления
split-by-sections.vertical.placeholder=Enter number of vertical divisions split-by-sections.vertical.placeholder=Въведете брой вертикални деления
split-by-sections.submit=Split PDF split-by-sections.submit=Разделяне на PDF
split-by-sections.merge=Merge Into One PDF split-by-sections.merge=Сливане в един PDF
#printFile
printFile.title=Печат на файл
printFile.header=Печат на файл на принтер
printFile.selectText.1=Изберете файл за печат
printFile.selectText.2=Въведете име на принтер
printFile.submit=Печат
#licenses #licenses
licenses.nav=Licenses licenses.nav=Лицензи
licenses.title=3rd Party Licenses licenses.title=Лицензи на трети страни
licenses.header=3rd Party Licenses licenses.header=Лицензи на трети страни
licenses.module=Module licenses.module=Модул
licenses.version=Version licenses.version=Версия
licenses.license=License licenses.license=Лиценз
# error # error
error.sorry=Sorry for the issue! error.sorry=Извинете за проблема!
error.needHelp=Need help / Found an issue? error.needHelp=Нуждаете се от помощ / Открихте проблем?
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.contactTip=Ако все още имате проблеми, не се колебайте да се свържете с нас за помощ. Можете да изпратите запитване на нашата страница в GitHub или да се свържете с нас чрез Discord:
error.404.head=404 - Page Not Found | Oops, we tripped in the code! error.404.head=404 - Страницата не е намерена | Опа! Спънахме се в кода!
error.404.1=We can't seem to find the page you're looking for. error.404.1=Изглежда не можем да намерим страницата, която търсите.
error.404.2=Something went wrong error.404.2=Нещо се обърка
error.github=Submit a ticket on GitHub error.github=Изпратете запитване в GitHub
error.showStack=Show Stack Trace error.showStack=Покажи проследяване на стека
error.copyStack=Copy Stack Trace error.copyStack=Копиране на проследяване на стека
error.githubSubmit=GitHub - Submit a ticket error.githubSubmit=GitHub - Изпратете запитване
error.discordSubmit=Discord - Submit Support post error.discordSubmit=Discord - Изпратете запитване за поддръжка

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Opcions
############# #############
settings.title=Opcions settings.title=Opcions
settings.update=Actualització Disponible settings.update=Actualització Disponible
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Versió App: 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.title=Trieu l'opció de descàrrega (per a descàrregues d'un sol fitxer no zip):
settings.downloadOption.1=Obre mateixa finestra settings.downloadOption.1=Obre mateixa finestra
@ -120,8 +123,9 @@ settings.downloadOption.3=Descarrega Arxiu
settings.zipThreshold=Comprimiu els fitxers quan el nombre de fitxers baixats superi settings.zipThreshold=Comprimiu els fitxers quan el nombre de fitxers baixats superi
settings.signOut=Sortir settings.signOut=Sortir
settings.accountSettings=Account Settings 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Desar Usuari adminUserSettings.submit=Desar Usuari
adminUserSettings.changeUserRole=Canvia el rol de l'usuari
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Recordar
login.invalid=Nom usuari / password no vàlid login.invalid=Nom usuari / password no vàlid
login.locked=Compte bloquejat login.locked=Compte bloquejat
login.signinTitle=Autenticat 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 #auto-redact
@ -687,6 +694,7 @@ repair.submit=Reparar
#flatten #flatten
flatten.title=Aplanar flatten.title=Aplanar
flatten.header=Aplana els PDF flatten.header=Aplana els PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Aplanar flatten.submit=Aplanar
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF a PDF/A
pdfToPDFA.credit=Utilitza OCRmyPDF per la conversió a PDF/A pdfToPDFA.credit=Utilitza OCRmyPDF per la conversió a PDF/A
pdfToPDFA.submit=Converteix pdfToPDFA.submit=Converteix
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -46,7 +46,7 @@ green=Grün
blue=Blau blue=Blau
custom=benutzerdefiniert... custom=benutzerdefiniert...
WorkInProgess=In Arbeit, funktioniert möglicherweise nicht oder ist fehlerhaft. Bitte melden Sie alle Probleme! WorkInProgess=In Arbeit, funktioniert möglicherweise nicht oder ist fehlerhaft. Bitte melden Sie alle Probleme!
poweredBy=Powered by poweredBy=Unterstützt von
yes=Ja yes=Ja
no=Nein no=Nein
changedCredsMessage=Anmeldedaten geändert! changedCredsMessage=Anmeldedaten geändert!
@ -57,6 +57,8 @@ usernameExistsMessage=Neuer Benutzername existiert bereits.
invalidUsernameMessage=Ungültiger Benutzername. Der Benutzername darf nur Buchstaben und Zahlen enthalten. invalidUsernameMessage=Ungültiger Benutzername. Der Benutzername darf nur Buchstaben und Zahlen enthalten.
deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht werden. deleteCurrentUserMessage=Der aktuell angemeldete Benutzer kann nicht gelöscht werden.
deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden. deleteUsernameExistsMessage=Der Benutzername existiert nicht und kann nicht gelöscht werden.
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 error=Fehler
oops=Hoppla! oops=Hoppla!
help=Hilfe help=Hilfe
@ -112,6 +114,7 @@ navbar.settings=Einstellungen
############# #############
settings.title=Einstellungen settings.title=Einstellungen
settings.update=Update verfügbar 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.appVersion=App-Version:
settings.downloadOption.title=Download-Option wählen (für einzelne Dateien, die keine Zip-Downloads sind): settings.downloadOption.title=Download-Option wählen (für einzelne Dateien, die keine Zip-Downloads sind):
settings.downloadOption.1=Im selben Fenster öffnen settings.downloadOption.1=Im selben Fenster öffnen
@ -120,8 +123,9 @@ settings.downloadOption.3=Datei herunterladen
settings.zipThreshold=Dateien komprimieren, wenn die Anzahl der heruntergeladenen Dateien überschritten wird settings.zipThreshold=Dateien komprimieren, wenn die Anzahl der heruntergeladenen Dateien überschritten wird
settings.signOut=Abmelden settings.signOut=Abmelden
settings.accountSettings=Kontoeinstellungen 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.title=Anmeldeinformationen ändern
changeCreds.header=Aktualisieren Sie Ihre Kontodaten changeCreds.header=Aktualisieren Sie Ihre Kontodaten
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo-Benutzer (Keine benutzerdefinierten Einstellunge
adminUserSettings.internalApiUser=Interner API-Benutzer adminUserSettings.internalApiUser=Interner API-Benutzer
adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern adminUserSettings.forceChange=Benutzer dazu zwingen, Benutzernamen/Passwort bei der Anmeldung zu ändern
adminUserSettings.submit=Benutzer speichern adminUserSettings.submit=Benutzer speichern
adminUserSettings.changeUserRole=Benutzerrolle ändern
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -188,58 +193,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 multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side
home.merge.title=Zusammenführen 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 merge.tags=zusammenführen,seitenvorgänge,back end,serverseite
home.split.title=Aufteilen 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 split.tags=seitenoperationen,teilen,mehrseitig,ausschneiden,serverseitig
home.rotate.title=Drehen 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 rotate.tags=serverseitig
home.imageToPdf.title=Bild zu PDF 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 imageToPdf.tags=konvertierung,img,jpg,bild,foto
home.pdfToImage.title=PDF zu Bild 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 pdfToImage.tags=konvertierung,img,jpg,bild,foto
home.pdfOrganiser.title=Organisieren 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 pdfOrganiser.tags=duplex,gerade,ungerade,sortieren,verschieben
home.addImage.title=Bild einfügen 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 addImage.tags=img,jpg,bild,foto
home.watermark.title=Wasserzeichen hinzufügen 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 watermark.tags=text,wiederholend,beschriftung,besitzen,urheberrecht,marke,img,jpg,bild,foto
home.permissions.title=Berechtigungen ändern 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 permissions.tags=lesen,schreiben,bearbeiten,drucken
home.removePages.title=Entfernen 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 removePages.tags=seiten entfernen,seiten löschen
home.addPassword.title=Passwort hinzufügen 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 addPassword.tags=sicher,sicherheit
home.removePassword.title=Passwort entfernen 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 removePassword.tags=sichern,entschlüsseln,sicherheit,passwort aufheben,passwort löschen
home.compressPdfs.title=Komprimieren 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 compressPdfs.tags=komprimieren,verkleinern,minimieren
@ -252,7 +257,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 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.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 ocr.tags=erkennung,text,bild,scannen,lesen,identifizieren,erkennung,bearbeitbar
@ -435,6 +440,8 @@ login.rememberme=Angemeldet bleiben
login.invalid=Benutzername oder Passwort ungültig. login.invalid=Benutzername oder Passwort ungültig.
login.locked=Ihr Konto wurde gesperrt. login.locked=Ihr Konto wurde gesperrt.
login.signinTitle=Bitte melden Sie sich an. login.signinTitle=Bitte melden Sie sich an.
login.ssoSignIn=Anmeldung per Single Sign-On
login.oauth2AutoCreateDisabled=OAUTH2 Benutzer automatisch erstellen deaktiviert
#auto-redact #auto-redact
@ -445,7 +452,7 @@ autoRedact.textsToRedactLabel=Zu zensierender Text (einer pro Zeile)
autoRedact.textsToRedactPlaceholder=z.B. \nVertraulich \nStreng geheim autoRedact.textsToRedactPlaceholder=z.B. \nVertraulich \nStreng geheim
autoRedact.useRegexLabel=Regex verwenden autoRedact.useRegexLabel=Regex verwenden
autoRedact.wholeWordSearchLabel=Ganzes Wort suchen 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.convertPDFToImageLabel=PDF in PDF-Bild konvertieren (zum Entfernen von Text hinter dem Kasten)
autoRedact.submitButton=Zensieren autoRedact.submitButton=Zensieren
@ -499,16 +506,16 @@ HTMLToPDF.header=HTML zu PDF
HTMLToPDF.help=Akzeptiert HTML-Dateien und ZIPs mit html/css/images etc. HTMLToPDF.help=Akzeptiert HTML-Dateien und ZIPs mit html/css/images etc.
HTMLToPDF.submit=Konvertieren HTMLToPDF.submit=Konvertieren
HTMLToPDF.credit=Verwendet WeasyPrint HTMLToPDF.credit=Verwendet WeasyPrint
HTMLToPDF.zoom=Zoomstufe zur Darstellung der Website. HTMLToPDF.zoom=Zoomstufe zur Darstellung der Website
HTMLToPDF.pageWidth=Breite der Seite in Zentimetern. (Leer auf Standard) HTMLToPDF.pageWidth=Breite der Seite in Zentimetern (Leer auf Standard)
HTMLToPDF.pageHeight=Höhe 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.marginTop=Oberer Rand der Seite in Millimetern (Leer auf Standard)
HTMLToPDF.marginBottom=Unterer 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.marginLeft=Linker Rand der Seite in Millimetern (Leer auf Standard)
HTMLToPDF.marginRight=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.printBackground=Den Hintergrund der Website rendern
HTMLToPDF.defaultHeader=Standardkopfzeile aktivieren (Name und Seitenzahl) 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.none=Keine
HTMLToPDF.print=Drucken HTMLToPDF.print=Drucken
HTMLToPDF.screen=Bildschirm HTMLToPDF.screen=Bildschirm
@ -609,8 +616,8 @@ pageLayout.submit=Abschicken
#scalePages #scalePages
scalePages.title=Seitengröße anpassen scalePages.title=Seitengröße anpassen
scalePages.header=Seitengröße anpassen scalePages.header=Seitengröße anpassen
scalePages.pageSize=Format der Seiten des Dokuments. scalePages.pageSize=Format der Seiten des Dokuments
scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite. scalePages.scaleFactor=Zoomstufe (Ausschnitt) einer Seite
scalePages.submit=Abschicken scalePages.submit=Abschicken
@ -687,6 +694,7 @@ repair.submit=Reparieren
#flatten #flatten
flatten.title=Abflachen flatten.title=Abflachen
flatten.header=PDFs reduzieren flatten.header=PDFs reduzieren
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Abflachen flatten.submit=Abflachen
@ -753,7 +761,7 @@ compress.submit=Komprimieren
#Add image #Add image
addImage.title=Bild hinzufügen addImage.title=Bild hinzufügen
addImage.header=Ein Bild einfügen addImage.header=Ein Bild einfügen
addImage.everyPage=Jede Seite? addImage.everyPage=In jede Seite einfügen?
addImage.upload=Bild hinzufügen addImage.upload=Bild hinzufügen
addImage.submit=Bild hinzufügen addImage.submit=Bild hinzufügen
@ -938,7 +946,8 @@ pdfToPDFA.title=PDF zu PDF/A
pdfToPDFA.header=PDF zu PDF/A pdfToPDFA.header=PDF zu PDF/A
pdfToPDFA.credit=Dieser Dienst verwendet OCRmyPDF für die PDF/A-Konvertierung pdfToPDFA.credit=Dieser Dienst verwendet OCRmyPDF für die PDF/A-Konvertierung
pdfToPDFA.submit=Konvertieren 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 #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Anzahl vertikaler Teiler eingeben
split-by-sections.submit=PDF teilen split-by-sections.submit=PDF teilen
split-by-sections.merge=In eine PDF zusammenfügen 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
licenses.nav=Lizenzen licenses.nav=Lizenzen
licenses.title=Lizenzen von Drittanbietern licenses.title=Lizenzen von Drittanbietern

View File

@ -57,6 +57,8 @@ usernameExistsMessage=Το νέο όνομα χρήστη υπάρχει ήδη.
invalidUsernameMessage=Μη έγκυρο όνομα χρήστη, το όνομα χρήστη πρέπει να περιέχει μόνο αλφαβητικούς χαρακτήρες και αριθμούς. invalidUsernameMessage=Μη έγκυρο όνομα χρήστη, το όνομα χρήστη πρέπει να περιέχει μόνο αλφαβητικούς χαρακτήρες και αριθμούς.
deleteCurrentUserMessage=Δεν είναι δυνατή η διαγραφή του τρέχοντος συνδεδεμένου χρήστη. deleteCurrentUserMessage=Δεν είναι δυνατή η διαγραφή του τρέχοντος συνδεδεμένου χρήστη.
deleteUsernameExistsMessage=Το όνομα χρήστη δεν υπάρχει και δεν μπορεί να διαγραφεί. deleteUsernameExistsMessage=Το όνομα χρήστη δεν υπάρχει και δεν μπορεί να διαγραφεί.
downgradeCurrentUserMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη
downgradeCurrentUserLongMessage=Δεν είναι δυνατή η υποβάθμιση του ρόλου του τρέχοντος χρήστη. Ως εκ τούτου, ο τρέχων χρήστης δεν θα εμφανίζεται.
error=Σφάλμα error=Σφάλμα
oops=Ωχ! oops=Ωχ!
help=Βοήθεια help=Βοήθεια
@ -112,6 +114,7 @@ navbar.settings=Ρυθμίσεις
############# #############
settings.title=Ρυθμίσεις settings.title=Ρυθμίσεις
settings.update=Υπάρχει διαθέσιμη ενημέρωση settings.update=Υπάρχει διαθέσιμη ενημέρωση
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Έκδοση εφαρμογής: settings.appVersion=Έκδοση εφαρμογής:
settings.downloadOption.title=Επιλέξετε την επιλογή λήψης (Για λήψεις μεμονωμένων αρχείων χωρίς zip): settings.downloadOption.title=Επιλέξετε την επιλογή λήψης (Για λήψεις μεμονωμένων αρχείων χωρίς zip):
settings.downloadOption.1=Άνοιγμα στο ίδιο παράθυρο settings.downloadOption.1=Άνοιγμα στο ίδιο παράθυρο
@ -120,8 +123,9 @@ settings.downloadOption.3=Λήψη αρχείου
settings.zipThreshold=Αρχεία Zip όταν ο αριθμός των ληφθέντων αρχείων είναι πολύ μεγάλος settings.zipThreshold=Αρχεία Zip όταν ο αριθμός των ληφθέντων αρχείων είναι πολύ μεγάλος
settings.signOut=Αποσύνδεση settings.signOut=Αποσύνδεση
settings.accountSettings=Ρυθμίσεις Λογαριασμού 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.title=Αλλαγή Διαπιστευτηρίων
changeCreds.header=Ενημέρωση των λεπτομερειών του Λογαριασμού σας changeCreds.header=Ενημέρωση των λεπτομερειών του Λογαριασμού σας
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo χρήστης (Χωρίς προσαρμοσμ
adminUserSettings.internalApiUser=Εσωτερικός API χρήστης adminUserSettings.internalApiUser=Εσωτερικός API χρήστης
adminUserSettings.forceChange=Αναγκάστε τον χρήστη να αλλάξει το όνομα χρήστη/κωδικό πρόσβασης κατά τη σύνδεση adminUserSettings.forceChange=Αναγκάστε τον χρήστη να αλλάξει το όνομα χρήστη/κωδικό πρόσβασης κατά τη σύνδεση
adminUserSettings.submit=Αποθήκευση Χρήστη adminUserSettings.submit=Αποθήκευση Χρήστη
adminUserSettings.changeUserRole=Αλλαγή ρόλου χρήστη
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Να Με Θυμάσαι
login.invalid=Λάθος όνομα χρήστη ή κωδικού πρόσβασης. login.invalid=Λάθος όνομα χρήστη ή κωδικού πρόσβασης.
login.locked=Ο λογαριασμός σας έχει κλειδωθεί. login.locked=Ο λογαριασμός σας έχει κλειδωθεί.
login.signinTitle=Παρακαλώ, συνδεθείτε login.signinTitle=Παρακαλώ, συνδεθείτε
login.ssoSignIn=Σύνδεση μέσω μοναδικής σύνδεσης
login.oauth2AutoCreateDisabled=Απενεργοποιήθηκε ο χρήστης αυτόματης δημιουργίας OAUTH2
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Επιδιόρθωση
#flatten #flatten
flatten.title=Flatten flatten.title=Flatten
flatten.header=Flatten PDFs flatten.header=Flatten PDFs
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Flatten flatten.submit=Flatten
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF σε PDF/A
pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF για PDF/A μετατροπή pdfToPDFA.credit=Αυτή η υπηρεσία χρησιμοποιεί OCRmyPDF για PDF/A μετατροπή
pdfToPDFA.submit=Μετατροπή pdfToPDFA.submit=Μετατροπή
pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα pdfToPDFA.tip=Προς το παρόν δεν λειτουργεί για πολλαπλές εισόδους ταυτόχρονα
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Εισαγάγετε τον αριθμό
split-by-sections.submit=Διαχωρισμός PDF split-by-sections.submit=Διαχωρισμός PDF
split-by-sections.merge=Συγχώνευση σε ένα 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
licenses.nav=Άδειες licenses.nav=Άδειες
licenses.title=3rd Party Άδειες licenses.title=3rd Party Άδειες

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Settings
############# #############
settings.title=Settings settings.title=Settings
settings.update=Update available settings.update=Update available
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=App Version: settings.appVersion=App Version:
settings.downloadOption.title=Choose download option (For single file non zip downloads): settings.downloadOption.title=Choose download option (For single file non zip downloads):
settings.downloadOption.1=Open in same window settings.downloadOption.1=Open in same window
@ -120,8 +123,9 @@ settings.downloadOption.3=Download file
settings.zipThreshold=Zip files when the number of downloaded files exceeds settings.zipThreshold=Zip files when the number of downloaded files exceeds
settings.signOut=Sign Out settings.signOut=Sign Out
settings.accountSettings=Account Settings 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Change User's Role
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Remember me
login.invalid=Invalid username or password. login.invalid=Invalid username or password.
login.locked=Your account has been locked. login.locked=Your account has been locked.
login.signinTitle=Please sign in login.signinTitle=Please sign in
login.ssoSignIn=Login via Single Sign-on
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
#auto-redact #auto-redact
@ -686,7 +693,8 @@ repair.submit=Repair
#flatten #flatten
flatten.title=Flatten flatten.title=Flatten
flatten.header=Flatten PDFs flatten.header=Flatten PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Flatten flatten.submit=Flatten
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF To PDF/A
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
pdfToPDFA.submit=Convert pdfToPDFA.submit=Convert
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Settings
############# #############
settings.title=Settings settings.title=Settings
settings.update=Update available settings.update=Update available
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=App Version: settings.appVersion=App Version:
settings.downloadOption.title=Choose download option (For single file non zip downloads): settings.downloadOption.title=Choose download option (For single file non zip downloads):
settings.downloadOption.1=Open in same window settings.downloadOption.1=Open in same window
@ -120,8 +123,9 @@ settings.downloadOption.3=Download file
settings.zipThreshold=Zip files when the number of downloaded files exceeds settings.zipThreshold=Zip files when the number of downloaded files exceeds
settings.signOut=Sign Out settings.signOut=Sign Out
settings.accountSettings=Account Settings 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Change User's Role
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Remember me
login.invalid=Invalid username or password. login.invalid=Invalid username or password.
login.locked=Your account has been locked. login.locked=Your account has been locked.
login.signinTitle=Please sign in login.signinTitle=Please sign in
login.ssoSignIn=Login via Single Sign-on
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User Disabled
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Repair
#flatten #flatten
flatten.title=Flatten flatten.title=Flatten
flatten.header=Flatten PDFs flatten.header=Flatten PDFs
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Flatten flatten.submit=Flatten
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF To PDF/A
pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion pdfToPDFA.credit=This service uses OCRmyPDF for PDF/A conversion
pdfToPDFA.submit=Convert pdfToPDFA.submit=Convert
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -57,16 +57,18 @@ 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. 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. deleteCurrentUserMessage=No puede eliminar el usuario que tiene la sesión actualmente en uso.
deleteUsernameExistsMessage=El usuario no existe y no puede eliminarse. 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 error=Error
oops=Oops! oops=Ups!
help=Help help=Help
goHomepage=Go to Homepage goHomepage=Ir a la página principal
joinDiscord=Join our Discord server joinDiscord=Únase a nuestro servidor Discord
seeDockerHub=See Docker Hub seeDockerHub=Ver Docker Hub
visitGithub=Visit Github Repository visitGithub=Visitar Repositorio de Github
donate=Donate donate=Donar
color=Color color=Color
sponsor=Sponsor sponsor=Patrocinador
@ -78,8 +80,8 @@ pipeline.uploadButton=Cargar personalización
pipeline.configureButton=Configurar pipeline.configureButton=Configurar
pipeline.defaultOption=Personalizar pipeline.defaultOption=Personalizar
pipeline.submitButton=Enviar pipeline.submitButton=Enviar
pipeline.help=Pipeline Help pipeline.help=Ayuda de Canalización
pipeline.scanHelp=Folder Scanning Help pipeline.scanHelp=Ayuda de escaneado de carpetas
###################### ######################
# Pipeline Options # # Pipeline Options #
@ -112,6 +114,7 @@ navbar.settings=Configuración
############# #############
settings.title=Configuración settings.title=Configuración
settings.update=Actualización disponible 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.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.title=Elegir la opción de descarga (para descargas de un solo archivo sin ZIP):
settings.downloadOption.1=Abrir en la misma ventana settings.downloadOption.1=Abrir en la misma ventana
@ -120,8 +123,9 @@ settings.downloadOption.3=Descargar el archivo
settings.zipThreshold=Archivos ZIP cuando excede el número de archivos descargados settings.zipThreshold=Archivos ZIP cuando excede el número de archivos descargados
settings.signOut=Desconectar settings.signOut=Desconectar
settings.accountSettings=Configuración de la cuenta 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.title=Cambiar Credenciales
changeCreds.header=Actualice los detalles de su cuenta changeCreds.header=Actualice los detalles de su cuenta
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Usuario Demo (Sin ajustes personalizados)
adminUserSettings.internalApiUser=Usuario interno de API adminUserSettings.internalApiUser=Usuario interno de API
adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso adminUserSettings.forceChange=Forzar usuario a cambiar usuario/contraseña en el acceso
adminUserSettings.submit=Guardar Usuario adminUserSettings.submit=Guardar Usuario
adminUserSettings.changeUserRole=Cambiar rol de usuario
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -245,7 +250,7 @@ compressPdfs.tags=aplastar,pequeño,diminuto
home.changeMetadata.title=Cambiar metadatos home.changeMetadata.title=Cambiar metadatos
home.changeMetadata.desc=Cambiar/Eliminar/Añadir metadatos al documento PDF 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.title=Convertir archivo a PDF
home.fileToPDF.desc=Convertir casi cualquier archivo a PDF (DOCX, PNG, XLS, PPT, TXT y más) home.fileToPDF.desc=Convertir casi cualquier archivo a PDF (DOCX, PNG, XLS, PPT, TXT y más)
@ -435,6 +440,8 @@ login.rememberme=Recordarme
login.invalid=Nombre de usuario o contraseña erróneos. login.invalid=Nombre de usuario o contraseña erróneos.
login.locked=Su cuenta se ha bloqueado. login.locked=Su cuenta se ha bloqueado.
login.signinTitle=Por favor, inicie sesión 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 #auto-redact
@ -687,6 +694,7 @@ repair.submit=Reparar
#flatten #flatten
flatten.title=Aplanar flatten.title=Aplanar
flatten.header=Acoplar archivos PDF flatten.header=Acoplar archivos PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Aplanar flatten.submit=Aplanar
@ -770,23 +778,23 @@ merge.submit=Unir
pdfOrganiser.title=Organizador de páginas pdfOrganiser.title=Organizador de páginas
pdfOrganiser.header=Organizador de páginas PDF pdfOrganiser.header=Organizador de páginas PDF
pdfOrganiser.submit=Organizar páginas pdfOrganiser.submit=Organizar páginas
pdfOrganiser.mode=Mode pdfOrganiser.mode=Modo
pdfOrganiser.mode.1=Custom Page Order pdfOrganiser.mode.1=Orden de páginas personalizado
pdfOrganiser.mode.2=Reverse Order pdfOrganiser.mode.2=Orden inverso
pdfOrganiser.mode.3=Duplex Sort pdfOrganiser.mode.3=Ordenar dúplex
pdfOrganiser.mode.4=Booklet Sort pdfOrganiser.mode.4=Ordenar folleto
pdfOrganiser.mode.5=Side Stitch Booklet Sort pdfOrganiser.mode.5=Orden de folleto de encuadernado lateral
pdfOrganiser.mode.6=Odd-Even Split pdfOrganiser.mode.6=División par-impar
pdfOrganiser.mode.7=Remove First pdfOrganiser.mode.7=Quitar primera
pdfOrganiser.mode.8=Remove Last pdfOrganiser.mode.8=Quitar última
pdfOrganiser.mode.9=Remove First and Last pdfOrganiser.mode.9=Quitar primera y última
pdfOrganiser.placeholder=(e.g. 1,3,2 or 4-8,2,10-12 or 2n-1) pdfOrganiser.placeholder=(por ej., 1,3,2 o 4-8,2,10-12 o 2n-1)
#multiTool #multiTool
multiTool.title=Multi-herramienta PDF multiTool.title=Multi-herramienta PDF
multiTool.header=Multi-herramienta PDF multiTool.header=Multi-herramienta PDF
multiTool.uploadPrompts=Please Upload PDF multiTool.uploadPrompts=Por favor, cargue PDF
#view pdf #view pdf
viewPdf.title=Ver PDF viewPdf.title=Ver PDF
@ -885,8 +893,8 @@ watermark.selectText.7=Opacidad (0% - 100%):
watermark.selectText.8=Tipo de marca de agua: watermark.selectText.8=Tipo de marca de agua:
watermark.selectText.9=Imagen de marca de agua: watermark.selectText.9=Imagen de marca de agua:
watermark.submit=Añadir marca de agua watermark.submit=Añadir marca de agua
watermark.type.1=Text watermark.type.1=Texto
watermark.type.2=Image watermark.type.2=Imagen
#Change permissions #Change permissions
@ -938,7 +946,8 @@ pdfToPDFA.title=PDF a PDF/A
pdfToPDFA.header=PDF a PDF/A pdfToPDFA.header=PDF a PDF/A
pdfToPDFA.credit=Este servicio usa OCRmyPDF para la conversión a PDF/A pdfToPDFA.credit=Este servicio usa OCRmyPDF para la conversión a PDF/A
pdfToPDFA.submit=Convertir 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 #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Introduzca el número de divisiones verti
split-by-sections.submit=Dividir PDF split-by-sections.submit=Dividir PDF
split-by-sections.merge=Unir en Un 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
licenses.nav=Licencias licenses.nav=Licencias
licenses.title=Licencias de terceros licenses.title=Licencias de terceros
@ -1032,15 +1050,15 @@ licenses.license=Licencia
# error # error
error.sorry=Sorry for the issue! error.sorry=¡Perdón por el fallo!
error.needHelp=Need help / Found an issue? error.needHelp=Necesita ayuda / Encontró un fallo?
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.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 - Page Not Found | Oops, we tripped in the code! error.404.head=404 - Página no encontrada | Ups, tropezamos con el código!
error.404.1=We can't seem to find the page you're looking for. error.404.1=Parece que no podemos encontrar la página que está buscando.
error.404.2=Something went wrong error.404.2=Algo salió mal
error.github=Submit a ticket on GitHub error.github=Envíe un ticket en GitHub
error.showStack=Show Stack Trace error.showStack=Mostrar seguimiento de pila
error.copyStack=Copy Stack Trace error.copyStack=Mostrar seguimiento de pila
error.githubSubmit=GitHub - Submit a ticket error.githubSubmit=GitHub - Enviar un ticket
error.discordSubmit=Discord - Submit Support post error.discordSubmit=Discord - Enviar mensaje de soporte

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Ezarpenak
############# #############
settings.title=Ezarpenak settings.title=Ezarpenak
settings.update=Eguneratze eskuragarria settings.update=Eguneratze eskuragarria
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Aplikazioaren bertsioa: settings.appVersion=Aplikazioaren bertsioa:
settings.downloadOption.title=Hautatu deskargatzeko aukera (fitxategi bakarra deskargatzeko ZIP gabe): settings.downloadOption.title=Hautatu deskargatzeko aukera (fitxategi bakarra deskargatzeko ZIP gabe):
settings.downloadOption.1=Ireki leiho berean settings.downloadOption.1=Ireki leiho berean
@ -120,8 +123,9 @@ settings.downloadOption.3=Deskargatu fitxategia
settings.zipThreshold=ZIP fitxategiak deskargatutako fitxategi kopurua gainditzen denean settings.zipThreshold=ZIP fitxategiak deskargatutako fitxategi kopurua gainditzen denean
settings.signOut=Saioa itxi settings.signOut=Saioa itxi
settings.accountSettings=Kontuaren ezarpenak 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Gorde Erabiltzailea adminUserSettings.submit=Gorde Erabiltzailea
adminUserSettings.changeUserRole=Erabiltzailearen rola aldatu
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Oroitu nazazu
login.invalid=Okerreko erabiltzaile izena edo pasahitza. login.invalid=Okerreko erabiltzaile izena edo pasahitza.
login.locked=Zure kontua blokeatu egin da. login.locked=Zure kontua blokeatu egin da.
login.signinTitle=Mesedez, hasi saioa login.signinTitle=Mesedez, hasi saioa
login.ssoSignIn=Hasi saioa Saioa hasteko modu bakarraren bidez
login.oauth2AutoCreateDisabled=OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Konpondu
#flatten #flatten
flatten.title=Lautu flatten.title=Lautu
flatten.header=Akoplatu PDF fitxategiak flatten.header=Akoplatu PDF fitxategiak
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Lautu flatten.submit=Lautu
@ -939,6 +947,7 @@ pdfToPDFA.header=PDFa PDF/A bihurtu
pdfToPDFA.credit=Zerbitzu honek OCRmyPDF erabiltzen du PDFak PDF/A bihurtzeko pdfToPDFA.credit=Zerbitzu honek OCRmyPDF erabiltzen du PDFak PDF/A bihurtzeko
pdfToPDFA.submit=Bihurtu pdfToPDFA.submit=Bihurtu
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -57,6 +57,8 @@ usernameExistsMessage=Le nouveau nom dutilisateur existe déjà.
invalidUsernameMessage=Nom dutilisateur invalide, le nom dutilisateur ne peut contenir que des chiffres et des lettres. invalidUsernameMessage=Nom dutilisateur invalide, le nom dutilisateur ne peut contenir que des chiffres et des lettres.
deleteCurrentUserMessage=Impossible de supprimer lutilisateur actuellement connecté. deleteCurrentUserMessage=Impossible de supprimer lutilisateur actuellement connecté.
deleteUsernameExistsMessage=Le nom dutilisateur nexiste pas et ne peut pas être supprimé. deleteUsernameExistsMessage=Le nom dutilisateur nexiste 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 error=Erreur
oops=Oups ! oops=Oups !
help=Aide help=Aide
@ -112,6 +114,7 @@ navbar.settings=Paramètres
############# #############
settings.title=Paramètres settings.title=Paramètres
settings.update=Mise à jour disponible settings.update=Mise à jour disponible
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Version de lapplication : settings.appVersion=Version de lapplication :
settings.downloadOption.title=Choisissez loption de téléchargement (pour les téléchargements à fichier unique non ZIP) : settings.downloadOption.title=Choisissez loption de téléchargement (pour les téléchargements à fichier unique non ZIP) :
settings.downloadOption.1=Ouvrir dans la même fenêtre settings.downloadOption.1=Ouvrir dans la même fenêtre
@ -120,8 +123,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.zipThreshold=Compresser les fichiers en ZIP lorsque le nombre de fichiers téléchargés dépasse
settings.signOut=Déconnexion settings.signOut=Déconnexion
settings.accountSettings=Paramètres du compte 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.title=Modifiez vos identifiants
changeCreds.header=Mettez à jour vos identifiants de connexion changeCreds.header=Mettez à jour vos identifiants de connexion
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (Paramètres par défaut)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Forcer lutilisateur à changer son nom dutilisateur/mot de passe lors de la connexion adminUserSettings.forceChange=Forcer lutilisateur à changer son nom dutilisateur/mot de passe lors de la connexion
adminUserSettings.submit=Ajouter adminUserSettings.submit=Ajouter
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Se souvenir de moi
login.invalid=Nom dutilisateur ou mot de passe invalide. login.invalid=Nom dutilisateur ou mot de passe invalide.
login.locked=Votre compte a été verrouillé. login.locked=Votre compte a été verrouillé.
login.signinTitle=Veuillez vous connecter 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 #auto-redact
@ -687,6 +694,7 @@ repair.submit=Réparer
#flatten #flatten
flatten.title=Rendre inerte flatten.title=Rendre inerte
flatten.header=Rendre inerte flatten.header=Rendre inerte
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Rendre inerte flatten.submit=Rendre inerte
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF en PDF/A
pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion en PDF/A. pdfToPDFA.credit=Ce service utilise OCRmyPDF pour la conversion en PDF/A.
pdfToPDFA.submit=Convertir pdfToPDFA.submit=Convertir
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Entrer le nombre de divisions verticales
split-by-sections.submit=Diviser le PDF split-by-sections.submit=Diviser le PDF
split-by-sections.merge=Fusionner en un seul 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
licenses.nav=Licences licenses.nav=Licences
licenses.title=Licences tierces licenses.title=Licences tierces

View File

@ -57,6 +57,8 @@ usernameExistsMessage=नया उपयोगकर्ता नाम पह
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. deleteUsernameExistsMessage=The username does not exist and cannot be deleted.
downgradeCurrentUserMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता
downgradeCurrentUserLongMessage=मौजूदा यूज़र की भूमिका को डाउनग्रेड नहीं किया जा सकता। इसलिए, वर्तमान उपयोगकर्ता को नहीं दिखाया जाएगा।
error=Error error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=सेटिंग्स
############# #############
settings.title=सेटिंग्स settings.title=सेटिंग्स
settings.update=अपडेट उपलब्ध है settings.update=अपडेट उपलब्ध है
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=ऐप संस्करण: settings.appVersion=ऐप संस्करण:
settings.downloadOption.title=डाउनलोड विकल्प चुनें (एकल फ़ाइल गैर-ज़िप डाउनलोड के लिए): settings.downloadOption.title=डाउनलोड विकल्प चुनें (एकल फ़ाइल गैर-ज़िप डाउनलोड के लिए):
settings.downloadOption.1=एक ही विंडो में खोलें settings.downloadOption.1=एक ही विंडो में खोलें
@ -120,8 +123,9 @@ settings.downloadOption.3=फ़ाइल डाउनलोड करें
settings.zipThreshold=जब डाउनलोड की गई फ़ाइलों की संख्या सीमा से अधिक हो settings.zipThreshold=जब डाउनलोड की गई फ़ाइलों की संख्या सीमा से अधिक हो
settings.signOut=साइन आउट settings.signOut=साइन आउट
settings.accountSettings=खाता सेटिंग्स 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.title=क्रेडेंशियल बदलें
changeCreds.header=अपना खाता विवरण अपडेट करें changeCreds.header=अपना खाता विवरण अपडेट करें
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=उपयोगकर्ता को लॉगिन पर उपयोगकर्ता नाम/पासवर्ड बदलने के लिए मजबूर करें adminUserSettings.forceChange=उपयोगकर्ता को लॉगिन पर उपयोगकर्ता नाम/पासवर्ड बदलने के लिए मजबूर करें
adminUserSettings.submit=उपयोगकर्ता को सहेजें adminUserSettings.submit=उपयोगकर्ता को सहेजें
adminUserSettings.changeUserRole=यूज़र की भूमिका बदलें
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=मुझे याद रखें
login.invalid=अमान्य उपयोगकर्ता नाम या पासवर्ड। login.invalid=अमान्य उपयोगकर्ता नाम या पासवर्ड।
login.locked=आपका खाता लॉक कर दिया गया है। login.locked=आपका खाता लॉक कर दिया गया है।
login.signinTitle=कृपया साइन इन करें login.signinTitle=कृपया साइन इन करें
login.ssoSignIn=सिंगल साइन - ऑन के ज़रिए लॉग इन करें
login.oauth2AutoCreateDisabled=OAUTH2 ऑटो - क्रिएट यूज़र अक्षम किया गया
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=मरम्मत
#flatten #flatten
flatten.title=समतल करें flatten.title=समतल करें
flatten.header=पीडीएफ़ समतल करें flatten.header=पीडीएफ़ समतल करें
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=समतल करें flatten.submit=समतल करें
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF से PDF/A में
pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए OCRmyPDF का उपयोग किया जाता है। pdfToPDFA.credit=इस सेवा में PDF/A परिवर्तन के लिए OCRmyPDF का उपयोग किया जाता है।
pdfToPDFA.submit=परिवर्तित करें pdfToPDFA.submit=परिवर्तित करें
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=लंबवत विभाजन की
split-by-sections.submit=PDF को विभाजित करें split-by-sections.submit=PDF को विभाजित करें
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -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. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Beállítások
############# #############
settings.title=Beállítások settings.title=Beállítások
settings.update=Frisítés elérhető 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.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.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 settings.downloadOption.1=Nyissa meg ugyanabban az ablakban
@ -120,8 +123,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.zipThreshold=Fájlok tömörítése, ha a letöltött fájlok száma meghaladja
settings.signOut=Kijelentkezés settings.signOut=Kijelentkezés
settings.accountSettings=Fiókbeállítások 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.title=Hitelesítés megváltoztatása
changeCreds.header=Frissítse fiókadatait changeCreds.header=Frissítse fiókadatait
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User 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.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.submit=Felhasználó mentése
adminUserSettings.changeUserRole=Felhasználó szerepkörének módosítása
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Emlékezz rám
login.invalid=Érvénytelen felhasználónév vagy jelszó! login.invalid=Érvénytelen felhasználónév vagy jelszó!
login.locked=A fiókja zárolva lett! login.locked=A fiókja zárolva lett!
login.signinTitle=Kérjük, jelentkezzen be! 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 #auto-redact
@ -687,6 +694,7 @@ repair.submit=Javítás
#flatten #flatten
flatten.title=Kiegyenlítés flatten.title=Kiegyenlítés
flatten.header=PDF-ek kiegyenlítése flatten.header=PDF-ek kiegyenlítése
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Kiegyenlítés flatten.submit=Kiegyenlítés
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF >> PDF/A
pdfToPDFA.credit=Ez a szolgáltatás az OCRmyPDF-t használja a PDF/A konverzióhoz pdfToPDFA.credit=Ez a szolgáltatás az OCRmyPDF-t használja a PDF/A konverzióhoz
pdfToPDFA.submit=Konvertálás pdfToPDFA.submit=Konvertálás
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Adja meg a függőleges szakaszok számá
split-by-sections.submit=Felosztás split-by-sections.submit=Felosztás
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -57,6 +57,8 @@ usernameExistsMessage=Nama pengguna baru sudah ada.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Pengaturan
############# #############
settings.title=Pengaturan settings.title=Pengaturan
settings.update=Pembaruan tersedia settings.update=Pembaruan tersedia
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Versi Aplikasi: settings.appVersion=Versi Aplikasi:
settings.downloadOption.title=Pilih opsi unduhan (Untuk unduhan berkas tunggal non zip): settings.downloadOption.title=Pilih opsi unduhan (Untuk unduhan berkas tunggal non zip):
settings.downloadOption.1=Buka di jendela yang sama settings.downloadOption.1=Buka di jendela yang sama
@ -120,8 +123,9 @@ settings.downloadOption.3=Unduh berkas
settings.zipThreshold=Berkas zip ketika jumlah berkas yang diunduh melebihi settings.zipThreshold=Berkas zip ketika jumlah berkas yang diunduh melebihi
settings.signOut=Keluar settings.signOut=Keluar
settings.accountSettings=Pengaturan Akun 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.title=Ubah Kredensial
changeCreds.header=Perbarui Detail Akun Anda changeCreds.header=Perbarui Detail Akun Anda
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata sandi saat masuk adminUserSettings.forceChange=Memaksa pengguna untuk mengubah nama pengguna/kata sandi saat masuk
adminUserSettings.submit=Simpan Pengguna adminUserSettings.submit=Simpan Pengguna
adminUserSettings.changeUserRole=Ubah Peran Pengguna
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Ingat saya
login.invalid=Nama pengguna atau kata sandi tidak valid. login.invalid=Nama pengguna atau kata sandi tidak valid.
login.locked=Akun Anda telah dikunci. login.locked=Akun Anda telah dikunci.
login.signinTitle=Silakan masuk login.signinTitle=Silakan masuk
login.ssoSignIn=Masuk melalui Single Sign - on
login.oauth2AutoCreateDisabled=OAUTH2 Buat Otomatis Pengguna Dinonaktifkan
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Perbaiki
#flatten #flatten
flatten.title=Ratakan flatten.title=Ratakan
flatten.header=Ratakan PDF flatten.header=Ratakan PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Ratakan flatten.submit=Ratakan
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF ke PDF/A
pdfToPDFA.credit=Layanan ini menggunakan OCRmyPDF untuk konversi PDF/A. pdfToPDFA.credit=Layanan ini menggunakan OCRmyPDF untuk konversi PDF/A.
pdfToPDFA.submit=Konversi pdfToPDFA.submit=Konversi
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Input angka untuk pembagian vertikal
split-by-sections.submit=Pisahkan PDF split-by-sections.submit=Pisahkan PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -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. invalidUsernameMessage=Nome utente non valido, il nome utente deve contenere solo caratteri alfabetici e numeri.
deleteCurrentUserMessage=Impossibile eliminare l'utente attualmente connesso. deleteCurrentUserMessage=Impossibile eliminare l'utente attualmente connesso.
deleteUsernameExistsMessage=Il nome utente non esiste e non può essere eliminato. 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 error=Errore
oops=Oops! oops=Oops!
help=Aiuto help=Aiuto
@ -112,6 +114,7 @@ navbar.settings=Impostazioni
############# #############
settings.title=Impostazioni settings.title=Impostazioni
settings.update=Aggiornamento disponibile settings.update=Aggiornamento disponibile
settings.updateAvailable={0} è la versione attualmente installata. Una nuova versione ({1}) è disponibile.
settings.appVersion=Versione App: settings.appVersion=Versione App:
settings.downloadOption.title=Scegli opzione di download (Per file singoli non compressi): settings.downloadOption.title=Scegli opzione di download (Per file singoli non compressi):
settings.downloadOption.1=Apri in questa finestra settings.downloadOption.1=Apri in questa finestra
@ -120,8 +123,9 @@ settings.downloadOption.3=Scarica file
settings.zipThreshold=Comprimi file in .zip quando il numero di download supera settings.zipThreshold=Comprimi file in .zip quando il numero di download supera
settings.signOut=Logout settings.signOut=Logout
settings.accountSettings=Impostazioni Account 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.title=Cambia credenziali
changeCreds.header=Aggiorna i dettagli del tuo account changeCreds.header=Aggiorna i dettagli del tuo account
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Utente demo (nessuna impostazione personalizzata)
adminUserSettings.internalApiUser=API utente interna adminUserSettings.internalApiUser=API utente interna
adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso adminUserSettings.forceChange=Forza l'utente a cambiare nome username/password all'accesso
adminUserSettings.submit=Salva utente adminUserSettings.submit=Salva utente
adminUserSettings.changeUserRole=Cambia il ruolo dell'utente
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Ricordami
login.invalid=Nome utente o password errati. login.invalid=Nome utente o password errati.
login.locked=Il tuo account è stato bloccato. login.locked=Il tuo account è stato bloccato.
login.signinTitle=Per favore accedi login.signinTitle=Per favore accedi
login.ssoSignIn=Accedi tramite Single Sign-on
login.oauth2AutoCreateDisabled=Creazione automatica utente OAUTH2 DISABILITATA
#auto-redact #auto-redact
@ -644,7 +651,7 @@ removeBlanks.submit=Rimuovi
#removeAnnotations #removeAnnotations
removeAnnotations.title=Rimuovi Annotazioni removeAnnotations.title=Rimuovi Annotazioni
removeAnnotations.header=Remuovi Annotazioni removeAnnotations.header=Rimuovi Annotazioni
removeAnnotations.submit=Rimuovi removeAnnotations.submit=Rimuovi
@ -685,8 +692,9 @@ repair.submit=Ripara
#flatten #flatten
flatten.title=Appiattisci flatten.title=Appiattire
flatten.header=Appiattisci PDF flatten.header=Appiattisci PDF
flatten.flattenOnlyForms=Appiattisci solo i moduli
flatten.submit=Appiattisci flatten.submit=Appiattisci
@ -939,6 +947,7 @@ pdfToPDFA.header=Da PDF a PDF/A
pdfToPDFA.credit=Questo servizio utilizza OCRmyPDF per la conversione in PDF/A. pdfToPDFA.credit=Questo servizio utilizza OCRmyPDF per la conversione in PDF/A.
pdfToPDFA.submit=Converti pdfToPDFA.submit=Converti
pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente pdfToPDFA.tip=Attualmente non funziona per più input contemporaneamente
pdfToPDFA.outputFormat=Formato di output
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Inserire il numero di divisioni verticali
split-by-sections.submit=Dividi PDF split-by-sections.submit=Dividi PDF
split-by-sections.merge=Unisci in un unico 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
licenses.nav=Licenze licenses.nav=Licenze
licenses.title=Licenze di terze parti licenses.title=Licenze di terze parti

View File

@ -57,6 +57,8 @@ usernameExistsMessage=新しいユーザー名はすでに存在します。
invalidUsernameMessage=ユーザー名が無効です。ユーザー名にはアルファベットと数字のみを使用してください。 invalidUsernameMessage=ユーザー名が無効です。ユーザー名にはアルファベットと数字のみを使用してください。
deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。 deleteCurrentUserMessage=現在ログインしているユーザーは削除できません。
deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。 deleteUsernameExistsMessage=そのユーザー名は存在しないため削除できません。
downgradeCurrentUserMessage=現在のユーザーの役割をダウングレードできません
downgradeCurrentUserLongMessage=現在のユーザーの役割をダウングレードできません。したがって、現在のユーザーは表示されません。
error=エラー error=エラー
oops=おっと! oops=おっと!
help=ヘルプ help=ヘルプ
@ -112,6 +114,7 @@ navbar.settings=設定
############# #############
settings.title=設定 settings.title=設定
settings.update=利用可能なアップデート settings.update=利用可能なアップデート
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Appバージョン: settings.appVersion=Appバージョン:
settings.downloadOption.title=ダウンロードオプション (zip以外の単一ファイル): settings.downloadOption.title=ダウンロードオプション (zip以外の単一ファイル):
settings.downloadOption.1=同じウィンドウで開く settings.downloadOption.1=同じウィンドウで開く
@ -120,8 +123,9 @@ settings.downloadOption.3=ファイルをダウンロード
settings.zipThreshold=このファイル数を超えたときにファイルを圧縮する settings.zipThreshold=このファイル数を超えたときにファイルを圧縮する
settings.signOut=サインアウト settings.signOut=サインアウト
settings.accountSettings=アカウント設定 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.title=資格情報の変更
changeCreds.header=アカウントの詳細を更新する changeCreds.header=アカウントの詳細を更新する
@ -171,6 +175,7 @@ adminUserSettings.demoUser=デモユーザー (カスタム設定なし)
adminUserSettings.internalApiUser=内部APIユーザー adminUserSettings.internalApiUser=内部APIユーザー
adminUserSettings.forceChange=ログイン時にユーザー名/パスワードを強制的に変更する adminUserSettings.forceChange=ログイン時にユーザー名/パスワードを強制的に変更する
adminUserSettings.submit=ユーザーの保存 adminUserSettings.submit=ユーザーの保存
adminUserSettings.changeUserRole=ユーザーの役割を変更する
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=サインイン状態を記憶する
login.invalid=ユーザー名かパスワードが無効です。 login.invalid=ユーザー名かパスワードが無効です。
login.locked=あなたのアカウントはロックされています。 login.locked=あなたのアカウントはロックされています。
login.signinTitle=サインインしてください login.signinTitle=サインインしてください
login.ssoSignIn=シングルサインオンでログイン
login.oauth2AutoCreateDisabled=OAuth 2自動作成ユーザーが無効
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=修復
#flatten #flatten
flatten.title=平坦化 flatten.title=平坦化
flatten.header=PDFを平坦化する flatten.header=PDFを平坦化する
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=平坦化 flatten.submit=平坦化
@ -939,6 +947,7 @@ pdfToPDFA.header=PDFをPDF/Aに変換
pdfToPDFA.credit=本サービスはPDF/Aの変換にOCRmyPDFを使用しています。 pdfToPDFA.credit=本サービスはPDF/Aの変換にOCRmyPDFを使用しています。
pdfToPDFA.submit=変換 pdfToPDFA.submit=変換
pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません pdfToPDFA.tip=現在、一度に複数の入力に対して機能しません
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=垂直方向の分割数を選択
split-by-sections.submit=分割 split-by-sections.submit=分割
split-by-sections.merge=1 つの PDF に結合するかどうか 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
licenses.nav=ライセンス licenses.nav=ライセンス
licenses.title=サードパーティライセンス licenses.title=サードパーティライセンス

View File

@ -57,6 +57,8 @@ usernameExistsMessage=새 사용자명이 이미 존재합니다.
invalidUsernameMessage=사용자 이름이 잘못되었습니다. 사용자 이름에는 알파벳 문자와 숫자만 포함되어야 합니다. invalidUsernameMessage=사용자 이름이 잘못되었습니다. 사용자 이름에는 알파벳 문자와 숫자만 포함되어야 합니다.
deleteCurrentUserMessage=현재 로그인한 사용자를 삭제할 수 없습니다. deleteCurrentUserMessage=현재 로그인한 사용자를 삭제할 수 없습니다.
deleteUsernameExistsMessage=사용자 이름이 존재하지 않으며 삭제할 수 없습니다. deleteUsernameExistsMessage=사용자 이름이 존재하지 않으며 삭제할 수 없습니다.
downgradeCurrentUserMessage=현재 사용자의 역할을 다운그레이드할 수 없습니다
downgradeCurrentUserLongMessage=현재 사용자의 역할을 다운그레이드할 수 없습니다. 따라서 현재 사용자는 표시되지 않습니다.
error=오류 error=오류
oops=어머나! oops=어머나!
help=도움말 help=도움말
@ -112,6 +114,7 @@ navbar.settings=설정
############# #############
settings.title=설정 settings.title=설정
settings.update=업데이트 가능 settings.update=업데이트 가능
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=앱 버전: settings.appVersion=앱 버전:
settings.downloadOption.title=다운로드 옵션 선택 (zip 파일이 아닌 단일 파일 다운로드 시): settings.downloadOption.title=다운로드 옵션 선택 (zip 파일이 아닌 단일 파일 다운로드 시):
settings.downloadOption.1=현재 창에서 열기 settings.downloadOption.1=현재 창에서 열기
@ -120,8 +123,9 @@ settings.downloadOption.3=다운로드
settings.zipThreshold=다운로드한 파일 수가 초과된 경우 파일 압축하기 settings.zipThreshold=다운로드한 파일 수가 초과된 경우 파일 압축하기
settings.signOut=로그아웃 settings.signOut=로그아웃
settings.accountSettings=계정 설정 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.title=계정 정보 변경
changeCreds.header=계정 정보 업데이트 changeCreds.header=계정 정보 업데이트
@ -171,6 +175,7 @@ adminUserSettings.demoUser=데모 사용자(사용자 지정 설정 없음)
adminUserSettings.internalApiUser=내부 API 사용자 adminUserSettings.internalApiUser=내부 API 사용자
adminUserSettings.forceChange=다음 로그인 때 사용자명과 비밀번호를 변경하도록 강제 adminUserSettings.forceChange=다음 로그인 때 사용자명과 비밀번호를 변경하도록 강제
adminUserSettings.submit=사용자 저장 adminUserSettings.submit=사용자 저장
adminUserSettings.changeUserRole=사용자의 역할 변경
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=로그인 유지
login.invalid=사용자 이름이나 비밀번호가 틀립니다. login.invalid=사용자 이름이나 비밀번호가 틀립니다.
login.locked=계정이 잠겼습니다. login.locked=계정이 잠겼습니다.
login.signinTitle=로그인해 주세요. login.signinTitle=로그인해 주세요.
login.ssoSignIn=싱글사인온을 통한 로그인
login.oauth2AutoCreateDisabled=OAUTH2 사용자 자동 생성 비활성화됨
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=복구
#flatten #flatten
flatten.title=평탄화 flatten.title=평탄화
flatten.header=PDF 문서의 레이어 평탄화 flatten.header=PDF 문서의 레이어 평탄화
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=평탄화 flatten.submit=평탄화
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF 문서를 PDF/A로 변환
pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 OCRmyPDF 문서를 사용합니다. pdfToPDFA.credit=이 서비스는 PDF/A 변환을 위해 OCRmyPDF 문서를 사용합니다.
pdfToPDFA.submit=변환 pdfToPDFA.submit=변환
pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다. pdfToPDFA.tip=현재 한 번에 여러 입력에 대해 작동하지 않습니다.
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=수직 분할 수를 입력합니다
split-by-sections.submit=PDF 분할 split-by-sections.submit=PDF 분할
split-by-sections.merge=하나의 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
licenses.nav=라이센스 licenses.nav=라이센스
licenses.title=제3자 라이선스 licenses.title=제3자 라이선스

View File

@ -57,6 +57,8 @@ usernameExistsMessage=Nieuwe gebruikersnaam bestaat al.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Instellingen
############# #############
settings.title=Instellingen settings.title=Instellingen
settings.update=Update beschikbaar settings.update=Update beschikbaar
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=App versie: settings.appVersion=App versie:
settings.downloadOption.title=Kies download optie (Voor enkelvoudige bestanddownloads zonder zip): settings.downloadOption.title=Kies download optie (Voor enkelvoudige bestanddownloads zonder zip):
settings.downloadOption.1=Open in hetzelfde venster settings.downloadOption.1=Open in hetzelfde venster
@ -120,8 +123,9 @@ settings.downloadOption.3=Download bestand
settings.zipThreshold=Bestanden zippen wanneer het aantal gedownloade bestanden meer is dan settings.zipThreshold=Bestanden zippen wanneer het aantal gedownloade bestanden meer is dan
settings.signOut=Uitloggen settings.signOut=Uitloggen
settings.accountSettings=Account instellingen 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.title=Inloggegevens wijzigen
changeCreds.header=Werk je accountgegevens bij changeCreds.header=Werk je accountgegevens bij
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demogebruiker (geen aangepaste instellingen)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Forceer gebruiker om gebruikersnaam/wachtwoord te wijzigen bij inloggen adminUserSettings.forceChange=Forceer gebruiker om gebruikersnaam/wachtwoord te wijzigen bij inloggen
adminUserSettings.submit=Gebruiker opslaan adminUserSettings.submit=Gebruiker opslaan
adminUserSettings.changeUserRole=De rol van de gebruiker wijzigen
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Onthoud mij
login.invalid=Ongeldige gebruikersnaam of wachtwoord. login.invalid=Ongeldige gebruikersnaam of wachtwoord.
login.locked=Je account is geblokkeerd. login.locked=Je account is geblokkeerd.
login.signinTitle=Gelieve in te loggen login.signinTitle=Gelieve in te loggen
login.ssoSignIn=Inloggen via Single Sign-on
login.oauth2AutoCreateDisabled=OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Repareren
#flatten #flatten
flatten.title=Afvlakken flatten.title=Afvlakken
flatten.header=PDF's afvlakken flatten.header=PDF's afvlakken
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Afvlakken flatten.submit=Afvlakken
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF naar PDF/A
pdfToPDFA.credit=Deze service gebruikt OCRmyPDF voor PDF/A-conversie pdfToPDFA.credit=Deze service gebruikt OCRmyPDF voor PDF/A-conversie
pdfToPDFA.submit=Converteren pdfToPDFA.submit=Converteren
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Voer het aantal verticale secties in
split-by-sections.submit=PDF splitsen split-by-sections.submit=PDF splitsen
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenties licenses.nav=Licenties
licenses.title=Licenties van derden licenses.title=Licenties van derden

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Ustawienia
############# #############
settings.title=Ustawienia settings.title=Ustawienia
settings.update=Dostępna aktualizacja settings.update=Dostępna aktualizacja
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Wersia aplikacji: settings.appVersion=Wersia aplikacji:
settings.downloadOption.title=Wybierz opcję pobierania (w przypadku pobierania pojedynczych plików innych niż ZIP): settings.downloadOption.title=Wybierz opcję pobierania (w przypadku pobierania pojedynczych plików innych niż ZIP):
settings.downloadOption.1=Otwórz w tym samym oknie settings.downloadOption.1=Otwórz w tym samym oknie
@ -120,8 +123,9 @@ settings.downloadOption.3=Pobierz plik
settings.zipThreshold=Spakuj pliki, gdy liczba pobranych plików przekroczy settings.zipThreshold=Spakuj pliki, gdy liczba pobranych plików przekroczy
settings.signOut=Sign Out settings.signOut=Sign Out
settings.accountSettings=Account Settings 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Zmień rolę użytkownika
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Remember me
login.invalid=Invalid username or password. login.invalid=Invalid username or password.
login.locked=Your account has been locked. login.locked=Your account has been locked.
login.signinTitle=Please sign in login.signinTitle=Please sign in
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Napraw
#flatten #flatten
flatten.title=Spłaszcz flatten.title=Spłaszcz
flatten.header=Spłaszcz dokument(y) PDF flatten.header=Spłaszcz dokument(y) PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Spłaszcz flatten.submit=Spłaszcz
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF na PDF/A
pdfToPDFA.credit=Ta usługa używa OCRmyPDF do konwersji PDF/A pdfToPDFA.credit=Ta usługa używa OCRmyPDF do konwersji PDF/A
pdfToPDFA.submit=Konwertuj pdfToPDFA.submit=Konwertuj
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Configurações
############# #############
settings.title=Configurações settings.title=Configurações
settings.update=Atualização disponível 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.appVersion=Versão do aplicativo:
settings.downloadOption.title=Escolha a opção de download (para downloads não compactados de arquivo único): settings.downloadOption.title=Escolha a opção de download (para downloads não compactados de arquivo único):
settings.downloadOption.1=Abrir na mesma janela settings.downloadOption.1=Abrir na mesma janela
@ -120,8 +123,9 @@ settings.downloadOption.3=⇬ Fazer download do arquivo
settings.zipThreshold=Compactar arquivos quando o número de arquivos baixados exceder settings.zipThreshold=Compactar arquivos quando o número de arquivos baixados exceder
settings.signOut=Sign Out settings.signOut=Sign Out
settings.accountSettings=Account Settings 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Alterar Função de Usuário
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Remember me
login.invalid=Invalid username or password. login.invalid=Invalid username or password.
login.locked=Your account has been locked. login.locked=Your account has been locked.
login.signinTitle=Please sign in 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 #auto-redact
@ -687,6 +694,7 @@ repair.submit=Reparar
#flatten #flatten
flatten.title=Achatar flatten.title=Achatar
flatten.header=Achatar PDFs flatten.header=Achatar PDFs
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Achatar flatten.submit=Achatar
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF para PDF/A
pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A
pdfToPDFA.submit=Converter pdfToPDFA.submit=Converter
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -57,6 +57,8 @@ usernameExistsMessage=Esse utilizador já existe.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Configurações
############# #############
settings.title=Configurações settings.title=Configurações
settings.update=Atualização disponível 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.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.title=Escolha a opção de download (para downloads não compactados de ficheiro único):
settings.downloadOption.1=Abrir na mesma janela settings.downloadOption.1=Abrir na mesma janela
@ -120,8 +123,9 @@ settings.downloadOption.3=⇬ Fazer download do ficheiro
settings.zipThreshold=Compactar ficheiros quando o número de ficheiros baixados exceder settings.zipThreshold=Compactar ficheiros quando o número de ficheiros baixados exceder
settings.signOut=Terminar Sessão settings.signOut=Terminar Sessão
settings.accountSettings=Configuração de Conta 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.title=Alterar senha
changeCreds.header=Alterar dados da sua conta changeCreds.header=Alterar dados da sua conta
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Alterar usuário
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Lembrar dados
login.invalid=Utilizador ou senha inválidos. login.invalid=Utilizador ou senha inválidos.
login.locked=A sua conta foi bloqueada. login.locked=A sua conta foi bloqueada.
login.signinTitle=Introduza os seus dados de acesso 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 #auto-redact
@ -687,6 +694,7 @@ repair.submit=Reparar
#flatten #flatten
flatten.title=Achatar flatten.title=Achatar
flatten.header=Achatar PDFs flatten.header=Achatar PDFs
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Achatar flatten.submit=Achatar
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF para PDF/A
pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A pdfToPDFA.credit=Este serviço usa OCRmyPDF para Conversão de PDF/A
pdfToPDFA.submit=Converter pdfToPDFA.submit=Converter
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Introduza o número de divisões verticai
split-by-sections.submit=Dividir PDF split-by-sections.submit=Dividir PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenças licenses.nav=Licenças
licenses.title=Licenças de terceiros licenses.title=Licenças de terceiros

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Setări
############# #############
settings.title=Setări settings.title=Setări
settings.update=Actualizare disponibilă settings.update=Actualizare disponibilă
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Versiune aplicație: 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.title=Alege opțiunea de descărcare (pentru descărcarea unui singur fișier non-zip):
settings.downloadOption.1=Deschide în aceeași fereastră settings.downloadOption.1=Deschide în aceeași fereastră
@ -120,8 +123,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.zipThreshold=Împachetează fișierele când numărul de fișiere descărcate depășește
settings.signOut=Sign Out settings.signOut=Sign Out
settings.accountSettings=Account Settings 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Schimbați rolul utilizatorului
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Remember me
login.invalid=Invalid username or password. login.invalid=Invalid username or password.
login.locked=Your account has been locked. login.locked=Your account has been locked.
login.signinTitle=Please sign in login.signinTitle=Please sign in
login.ssoSignIn=Conectare prin conectare unică
login.oauth2AutoCreateDisabled=OAUTH2 Creare automată utilizator dezactivată
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Repară
#flatten #flatten
flatten.title=Nivelare flatten.title=Nivelare
flatten.header=Nivelează documente PDF flatten.header=Nivelează documente PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Nivelează flatten.submit=Nivelează
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF către PDF/A
pdfToPDFA.credit=Acest serviciu utilizează OCRmyPDF pentru conversia în PDF/A pdfToPDFA.credit=Acest serviciu utilizează OCRmyPDF pentru conversia în PDF/A
pdfToPDFA.submit=Convert pdfToPDFA.submit=Convert
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -57,6 +57,8 @@ usernameExistsMessage=Новое имя пользователя уже суще
invalidUsernameMessage=Недопустимое имя пользователя, Имя пользователя должно содержать только буквы алфавита и цифры. invalidUsernameMessage=Недопустимое имя пользователя, Имя пользователя должно содержать только буквы алфавита и цифры.
deleteCurrentUserMessage=Невозможно удалить пользователя, вошедшего в систему. deleteCurrentUserMessage=Невозможно удалить пользователя, вошедшего в систему.
deleteUsernameExistsMessage=Имя пользователя не существует и не может быть удалено. deleteUsernameExistsMessage=Имя пользователя не существует и не может быть удалено.
downgradeCurrentUserMessage=Невозможно понизить роль текущего пользователя
downgradeCurrentUserLongMessage=Невозможно понизить роль текущего пользователя. Следовательно, текущий пользователь не будет отображаться.
error=Ошибка error=Ошибка
oops=Ой! oops=Ой!
help=Помощь help=Помощь
@ -112,6 +114,7 @@ navbar.settings=Настройки
############# #############
settings.title=Настройки settings.title=Настройки
settings.update=Доступно обновление settings.update=Доступно обновление
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Версия приложения: settings.appVersion=Версия приложения:
settings.downloadOption.title=Выберите вариант загрузки (для загрузки одного файла без zip): settings.downloadOption.title=Выберите вариант загрузки (для загрузки одного файла без zip):
settings.downloadOption.1=Открыть в том же окне settings.downloadOption.1=Открыть в том же окне
@ -120,8 +123,9 @@ settings.downloadOption.3=Загрузить файл
settings.zipThreshold=Zip-файлы, когда количество загруженных файлов превышает settings.zipThreshold=Zip-файлы, когда количество загруженных файлов превышает
settings.signOut=Выйти settings.signOut=Выйти
settings.accountSettings=Настройки аккаунта 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.title=Изменить учетные данные
changeCreds.header=Обновите данные вашей учетной записи changeCreds.header=Обновите данные вашей учетной записи
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Демо-пользователь (без настр
adminUserSettings.internalApiUser=Внутренний пользователь API adminUserSettings.internalApiUser=Внутренний пользователь API
adminUserSettings.forceChange=Просить пользователя изменить пароль при входе в систему adminUserSettings.forceChange=Просить пользователя изменить пароль при входе в систему
adminUserSettings.submit=Сохранить пользователя adminUserSettings.submit=Сохранить пользователя
adminUserSettings.changeUserRole=Изменить роль пользователя
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Запомнить меня
login.invalid=Недействительное имя пользователя или пароль. login.invalid=Недействительное имя пользователя или пароль.
login.locked=Ваша учетная запись заблокирована. login.locked=Ваша учетная запись заблокирована.
login.signinTitle=Пожалуйста, войдите login.signinTitle=Пожалуйста, войдите
login.ssoSignIn=Вход через единый вход
login.oauth2AutoCreateDisabled=OAUTH2 Автоматическое создание пользователя отключено
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Ремонт
#flatten #flatten
flatten.title=Сглаживание flatten.title=Сглаживание
flatten.header=Сглаживание PDF ов flatten.header=Сглаживание PDF ов
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Сгладить flatten.submit=Сгладить
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF в PDF/A
pdfToPDFA.credit=Этот сервис использует OCRmyPDF для преобразования PDF/A pdfToPDFA.credit=Этот сервис использует OCRmyPDF для преобразования PDF/A
pdfToPDFA.submit=Конвертировать pdfToPDFA.submit=Конвертировать
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Введите количество ве
split-by-sections.submit=Разделить PDF split-by-sections.submit=Разделить PDF
split-by-sections.merge=Объединить в один 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
licenses.nav=Лицензии licenses.nav=Лицензии
licenses.title=Лицензии от третьих сторон licenses.title=Лицензии от третьих сторон

View File

@ -57,6 +57,8 @@ usernameExistsMessage=Novi korisnik već postoji
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Podešavanja
############# #############
settings.title=Podešavanja settings.title=Podešavanja
settings.update=Dostupno ažuriranje settings.update=Dostupno ažuriranje
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Verzija aplikacije: settings.appVersion=Verzija aplikacije:
settings.downloadOption.title=Odaberite opciju preuzimanja (Za preuzimanje pojedinačnih fajlova bez zip formata): settings.downloadOption.title=Odaberite opciju preuzimanja (Za preuzimanje pojedinačnih fajlova bez zip formata):
settings.downloadOption.1=Otvori u istom prozoru settings.downloadOption.1=Otvori u istom prozoru
@ -120,8 +123,9 @@ settings.downloadOption.3=Preuzmi fajl
settings.zipThreshold=Zipuj fajlove kada pređe broj preuzetih fajlova settings.zipThreshold=Zipuj fajlove kada pređe broj preuzetih fajlova
settings.signOut=Odjava settings.signOut=Odjava
settings.accountSettings=Podešavanja naloga 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.title=Promeni pristupne podatke
changeCreds.header=Ažurirajte detalje svog naloga changeCreds.header=Ažurirajte detalje svog naloga
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo korisnik (Bez prilagođenih podešavanja)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Prisili korisnika da promeni korisničko ime/lozinku pri prijavi adminUserSettings.forceChange=Prisili korisnika da promeni korisničko ime/lozinku pri prijavi
adminUserSettings.submit=Sačuvaj korisnika adminUserSettings.submit=Sačuvaj korisnika
adminUserSettings.changeUserRole=Promenite ulogu korisnika
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Zapamti me
login.invalid=Neispravno korisničko ime ili lozinka. login.invalid=Neispravno korisničko ime ili lozinka.
login.locked=Vaš nalog je zaključan. login.locked=Vaš nalog je zaključan.
login.signinTitle=Molimo vas da se prijavite 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 #auto-redact
@ -687,6 +694,7 @@ repair.submit=Popravi
#flatten #flatten
flatten.title=Ravnanje flatten.title=Ravnanje
flatten.header=Ravnanje PDF fajlova flatten.header=Ravnanje PDF fajlova
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Ravnanje flatten.submit=Ravnanje
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF u PDF/A
pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za konverziju u PDF/A format pdfToPDFA.credit=Ova usluga koristi OCRmyPDF za konverziju u PDF/A format
pdfToPDFA.submit=Konvertuj pdfToPDFA.submit=Konvertuj
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Unesite broj vertikalnih podele
split-by-sections.submit=Razdvoji PDF split-by-sections.submit=Razdvoji PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -57,6 +57,8 @@ usernameExistsMessage=New Username already exists.
invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers. invalidUsernameMessage=Invalid username, Username must only contain alphabet characters and numbers.
deleteCurrentUserMessage=Cannot delete currently logged in user. deleteCurrentUserMessage=Cannot delete currently logged in user.
deleteUsernameExistsMessage=The username does not exist and cannot be deleted. 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 error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Inställningar
############# #############
settings.title=Inställningar settings.title=Inställningar
settings.update=Uppdatering tillgänglig settings.update=Uppdatering tillgänglig
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Appversion: settings.appVersion=Appversion:
settings.downloadOption.title=Välj nedladdningsalternativ (för nedladdning av en fil utan zip): settings.downloadOption.title=Välj nedladdningsalternativ (för nedladdning av en fil utan zip):
settings.downloadOption.1=Öppnas i samma fönster settings.downloadOption.1=Öppnas i samma fönster
@ -120,8 +123,9 @@ settings.downloadOption.3=Ladda ner fil
settings.zipThreshold=Zip-filer när antalet nedladdade filer överskrider settings.zipThreshold=Zip-filer när antalet nedladdade filer överskrider
settings.signOut=Sign Out settings.signOut=Sign Out
settings.accountSettings=Account Settings 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.title=Change Credentials
changeCreds.header=Update Your Account Details changeCreds.header=Update Your Account Details
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo User (No custom settings)
adminUserSettings.internalApiUser=Internal API User adminUserSettings.internalApiUser=Internal API User
adminUserSettings.forceChange=Force user to change password on login adminUserSettings.forceChange=Force user to change password on login
adminUserSettings.submit=Save User adminUserSettings.submit=Save User
adminUserSettings.changeUserRole=Ändra användarens roll
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=Remember me
login.invalid=Invalid username or password. login.invalid=Invalid username or password.
login.locked=Your account has been locked. login.locked=Your account has been locked.
login.signinTitle=Please sign in login.signinTitle=Please sign in
login.ssoSignIn=Logga in via enkel inloggning
login.oauth2AutoCreateDisabled=OAUTH2 Auto-Create User inaktiverad
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=Reparera
#flatten #flatten
flatten.title=Platta till flatten.title=Platta till
flatten.header=Placera PDF-filer flatten.header=Placera PDF-filer
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Platta till flatten.submit=Platta till
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF till PDF/A
pdfToPDFA.credit=Denna tjänst använder OCRmyPDF för PDF/A-konvertering pdfToPDFA.credit=Denna tjänst använder OCRmyPDF för PDF/A-konvertering
pdfToPDFA.submit=Konvertera pdfToPDFA.submit=Konvertera
pdfToPDFA.tip=Currently does not work for multiple inputs at once pdfToPDFA.tip=Currently does not work for multiple inputs at once
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Enter number of vertical divisions
split-by-sections.submit=Split PDF split-by-sections.submit=Split PDF
split-by-sections.merge=Merge Into One 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
licenses.nav=Licenses licenses.nav=Licenses
licenses.title=3rd Party Licenses licenses.title=3rd Party Licenses

View File

@ -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. 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. deleteCurrentUserMessage=Şu anda oturum açmış olan kullanıcı silinemiyor.
deleteUsernameExistsMessage=Kullanıcı adı mevcut değil ve silinemez. 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 error=Hata
oops=Tüh! oops=Tüh!
help=Yardım help=Yardım
@ -66,31 +68,31 @@ seeDockerHub=Docker Hub'a bakın
visitGithub=Github Deposunu Ziyaret Edin visitGithub=Github Deposunu Ziyaret Edin
donate=Bağış Yapın donate=Bağış Yapın
color=Renk color=Renk
sponsor=Sponsor sponsor=Bağış
############### ###############
# Pipeline # # Pipeline #
############### ###############
pipeline.header=Pipeline Menü (Beta) pipeline.header=Çoklu İşlemler Menü (Beta)
pipeline.uploadButton=Upload edin pipeline.uploadButton=Upload edin
pipeline.configureButton=Yapılandır pipeline.configureButton=Yapılandır
pipeline.defaultOption=Özel pipeline.defaultOption=Özel
pipeline.submitButton=Gönder pipeline.submitButton=Gönder
pipeline.help=Pipeline Yardım pipeline.help=Çoklu İşlemler Yardım
pipeline.scanHelp=Klasör Tarama Yardımı pipeline.scanHelp=Klasör Tarama Yardımı
###################### ######################
# Pipeline Options # # Pipeline Options #
###################### ######################
pipelineOptions.header=Pipeline Yapılandırma pipelineOptions.header=Çoklu İşlemler Yapılandırma
pipelineOptions.pipelineNameLabel=Pipeline İsim pipelineOptions.pipelineNameLabel=Çoklu İşlemler İsim
pipelineOptions.saveSettings=Ayarları Kaydet pipelineOptions.saveSettings=Ayarları Kaydet
pipelineOptions.pipelineNamePrompt=Buraya isim girin pipelineOptions.pipelineNamePrompt=Buraya isim girin
pipelineOptions.selectOperation=İşlem Seçin pipelineOptions.selectOperation=İşlem Seçin
pipelineOptions.addOperationButton=İşlem ekle pipelineOptions.addOperationButton=İşlem ekle
pipelineOptions.pipelineHeader=Pipeline: pipelineOptions.pipelineHeader=Çoklu İşlemler:
pipelineOptions.saveButton=İndir pipelineOptions.saveButton=İndir
pipelineOptions.validateButton=Doğrula pipelineOptions.validateButton=Doğrula
@ -112,6 +114,7 @@ navbar.settings=Ayarlar
############# #############
settings.title=Ayarlar settings.title=Ayarlar
settings.update=Güncelleme mevcut 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.appVersion=Uygulama Sürümü:
settings.downloadOption.title=İndirme seçeneği seçin (Zip olmayan tek dosya indirmeler için): settings.downloadOption.title=İndirme seçeneği seçin (Zip olmayan tek dosya indirmeler için):
settings.downloadOption.1=Aynı pencerede aç settings.downloadOption.1=Aynı pencerede aç
@ -120,8 +123,9 @@ settings.downloadOption.3=Dosyayı indir
settings.zipThreshold=İndirilen dosya sayısı şu değeri aştığında zip dosyası oluştur: settings.zipThreshold=İndirilen dosya sayısı şu değeri aştığında zip dosyası oluştur:
settings.signOut=Oturumu Kapat settings.signOut=Oturumu Kapat
settings.accountSettings=Hesap Ayarları 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.title=Giriş Bilgilerini Değiştir
changeCreds.header=Hesap Detaylarınızı Güncelleyin changeCreds.header=Hesap Detaylarınızı Güncelleyin
@ -171,6 +175,7 @@ adminUserSettings.demoUser=Demo Kullanıcısı (Özel ayar yok)
adminUserSettings.internalApiUser=Dahili API Kullanıcısı adminUserSettings.internalApiUser=Dahili API Kullanıcısı
adminUserSettings.forceChange=Kullanıcının girişte kullanıcı adı/şifre değiştirmesini zorla adminUserSettings.forceChange=Kullanıcının girişte kullanıcı adı/şifre değiştirmesini zorla
adminUserSettings.submit=Kullanıcıyı Kaydet adminUserSettings.submit=Kullanıcıyı Kaydet
adminUserSettings.changeUserRole=Kullanıcı rolünü değiştir
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -325,8 +330,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 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 scalePages.tags=boyutlandır,değiştir,boyut,uyarla
home.pipeline.title=Hattı (İleri Seviye) home.pipeline.title=Çoklu İşlemler (İleri Seviye)
home.pipeline.desc=Hattı betikleri tanımlayarak PDF'lere birden fazla işlemi çalıştır 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 pipeline.tags=otomatikleştir,sıralı,betikli,toplu-işlem
home.add-page-numbers.title=Sayfa Numaraları Ekle home.add-page-numbers.title=Sayfa Numaraları Ekle
@ -435,6 +440,8 @@ login.rememberme=Beni hatırla
login.invalid=Geçersiz kullanıcı adı veya şifre. login.invalid=Geçersiz kullanıcı adı veya şifre.
login.locked=Hesabınız kilitlendi. login.locked=Hesabınız kilitlendi.
login.signinTitle=Lütfen giriş yapınız. 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 #auto-redact
@ -595,7 +602,7 @@ autoSplitPDF.submit=Gönder
#pipeline #pipeline
pipeline.title=Pipeline pipeline.title=Çoklu İşlemler
#pageLayout #pageLayout
@ -664,7 +671,7 @@ BookToPDF.submit=Dönüştür
#PDFToBook #PDFToBook
PDFToBook.title=PDF'den Kitaba PDFToBook.title=PDF'den Kitaba
PDFToBook.header=PDF'den Kitaba PDFToBook.header=PDF'den Kitaba
PDFToBook.selectText.1=Format PDFToBook.selectText.1=Format biçimi
PDFToBook.credit=Kalibre Kullanır PDFToBook.credit=Kalibre Kullanır
PDFToBook.submit=Dönüştür PDFToBook.submit=Dönüştür
@ -687,6 +694,7 @@ repair.submit=Onar
#flatten #flatten
flatten.title=Düzleştir flatten.title=Düzleştir
flatten.header=PDF'leri Düzleştir flatten.header=PDF'leri Düzleştir
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Düzleştir flatten.submit=Düzleştir
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF'den PDF/A'ya
pdfToPDFA.credit=Bu hizmet PDF/A dönüşümü için OCRmyPDF kullanır pdfToPDFA.credit=Bu hizmet PDF/A dönüşümü için OCRmyPDF kullanır
pdfToPDFA.submit=Dönüştür pdfToPDFA.submit=Dönüştür
pdfToPDFA.tip=Şu anda aynı anda birden fazla giriş için çalışmıyor pdfToPDFA.tip=Şu anda aynı anda birden fazla giriş için çalışmıyor
pdfToPDFA.outputFormat=Çıkış formatı
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Dikey bölme sayısını girin
split-by-sections.submit=PDF'yi Böl split-by-sections.submit=PDF'yi Böl
split-by-sections.merge=Bir PDF'de Birleştirin 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
licenses.nav=Lisanslar licenses.nav=Lisanslar
licenses.title=3. Taraf Lisansları licenses.title=3. Taraf Lisansları

View File

@ -57,6 +57,8 @@ usernameExistsMessage=Нове ім'я користувача вже існує.
invalidUsernameMessage=Недійсне ім'я користувача, Ім'я користувача повинно містити тільки літери алфавіту та цифри. invalidUsernameMessage=Недійсне ім'я користувача, Ім'я користувача повинно містити тільки літери алфавіту та цифри.
deleteCurrentUserMessage=Неможливо видалити користувача, який увійшов в систему. deleteCurrentUserMessage=Неможливо видалити користувача, який увійшов в систему.
deleteUsernameExistsMessage=Ім'я користувача не існує і не може бути видалено. deleteUsernameExistsMessage=Ім'я користувача не існує і не може бути видалено.
downgradeCurrentUserMessage=Неможливо понизити роль поточного користувача
downgradeCurrentUserLongMessage=Неможливо понизити роль поточного користувача. Отже, поточний користувач не відображатиметься.
error=Error error=Error
oops=Oops! oops=Oops!
help=Help help=Help
@ -112,6 +114,7 @@ navbar.settings=Налаштування
############# #############
settings.title=Налаштування settings.title=Налаштування
settings.update=Доступне оновлення settings.update=Доступне оновлення
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=Версія додатку: settings.appVersion=Версія додатку:
settings.downloadOption.title=Виберіть варіант завантаження (для завантаження одного файлу без zip): settings.downloadOption.title=Виберіть варіант завантаження (для завантаження одного файлу без zip):
settings.downloadOption.1=Відкрити в тому ж вікні settings.downloadOption.1=Відкрити в тому ж вікні
@ -120,8 +123,9 @@ settings.downloadOption.3=Завантажити файл
settings.zipThreshold=Zip-файли, коли кількість завантажених файлів перевищує settings.zipThreshold=Zip-файли, коли кількість завантажених файлів перевищує
settings.signOut=Вийти settings.signOut=Вийти
settings.accountSettings=Налаштування акаунта 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.title=Змінити облікові дані
changeCreds.header=Оновіть дані вашого облікового запису changeCreds.header=Оновіть дані вашого облікового запису
@ -132,6 +136,8 @@ changeCreds.newPassword=Новий пароль
changeCreds.confirmNewPassword=Підтвердіть новий пароль changeCreds.confirmNewPassword=Підтвердіть новий пароль
changeCreds.submit=Надіслати зміни changeCreds.submit=Надіслати зміни
account.title=Налаштування акаунта account.title=Налаштування акаунта
account.accountSettings=Налаштування акаунта account.accountSettings=Налаштування акаунта
account.adminSettings=Налаштування адміністратора - Перегляд і додавання користувачів account.adminSettings=Налаштування адміністратора - Перегляд і додавання користувачів
@ -152,6 +158,7 @@ account.webBrowserSettings=Налаштування веб-браузера
account.syncToBrowser=Синхронізувати обліковий запис -> Браузер account.syncToBrowser=Синхронізувати обліковий запис -> Браузер
account.syncToAccount=Синхронізувати обліковий запис <- Браузер account.syncToAccount=Синхронізувати обліковий запис <- Браузер
adminUserSettings.title=Налаштування контролю користувача adminUserSettings.title=Налаштування контролю користувача
adminUserSettings.header=Налаштування контролю користувача адміністратора adminUserSettings.header=Налаштування контролю користувача адміністратора
adminUserSettings.admin=Адміністратор adminUserSettings.admin=Адміністратор
@ -168,6 +175,7 @@ adminUserSettings.demoUser=Демо-користувач (без налашто
adminUserSettings.internalApiUser=Внутрішній користувач API adminUserSettings.internalApiUser=Внутрішній користувач API
adminUserSettings.forceChange=Примусити користувача змінити пароль при вході в систему adminUserSettings.forceChange=Примусити користувача змінити пароль при вході в систему
adminUserSettings.submit=Зберегти користувача adminUserSettings.submit=Зберегти користувача
adminUserSettings.changeUserRole=Змінити роль користувача
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -244,12 +252,10 @@ home.changeMetadata.title=Змінити метадані
home.changeMetadata.desc=Змінити/видалити/додати метадані з документа PDF home.changeMetadata.desc=Змінити/видалити/додати метадані з документа PDF
changeMetadata.tags=Title,author,date,creation,time,publisher,producer,stats changeMetadata.tags=Title,author,date,creation,time,publisher,producer,stats
home.fileToPDF.title=Конвертувати файл в PDF home.fileToPDF.title=Конвертувати файл в PDF
home.fileToPDF.desc=Конвертуйте майже будь-який файл в PDF (DOCX, PNG, XLS, PPT, TXT та інші) home.fileToPDF.desc=Конвертуйте майже будь-який файл в PDF (DOCX, PNG, XLS, PPT, TXT та інші)
fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint
home.ocr.title=OCR/Очищення сканування home.ocr.title=OCR/Очищення сканування
home.ocr.desc=Очищення сканування та виявлення тексту на зображеннях у файлі PDF та повторне додавання його як текст. home.ocr.desc=Очищення сканування та виявлення тексту на зображеннях у файлі PDF та повторне додавання його як текст.
ocr.tags=recognition,text,image,scan,read,identify,detection,editable ocr.tags=recognition,text,image,scan,read,identify,detection,editable
@ -403,12 +409,10 @@ home.overlay-pdfs.title=Накладення PDF
home.overlay-pdfs.desc=Накладення одного PDF поверх іншого PDF home.overlay-pdfs.desc=Накладення одного PDF поверх іншого PDF
overlay-pdfs.tags=Overlay overlay-pdfs.tags=Overlay
home.split-by-sections.title=Розділення PDF за секціями home.split-by-sections.title=Розділення PDF за секціями
home.split-by-sections.desc=Розділення кожної сторінки PDF на менші горизонтальні та вертикальні секції home.split-by-sections.desc=Розділення кожної сторінки PDF на менші горизонтальні та вертикальні секції
split-by-sections.tags=Section Split, Divide, Customize split-by-sections.tags=Section Split, Divide, Customize
home.AddStampRequest.title=Додати печатку на PDF home.AddStampRequest.title=Додати печатку на PDF
home.AddStampRequest.desc=Додавання текстової або зображення печатки у вказані місця home.AddStampRequest.desc=Додавання текстової або зображення печатки у вказані місця
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
@ -418,11 +422,11 @@ home.PDFToBook.title=PDF у книгу/комікс
home.PDFToBook.desc=Конвертує PDF у формат книги/комікса за допомогою calibre home.PDFToBook.desc=Конвертує PDF у формат книги/комікса за допомогою calibre
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
home.BookToPDF.title=Книга у PDF home.BookToPDF.title=Книга у PDF
home.BookToPDF.desc=Конвертує формати книги/комікса у PDF за допомогою calibre home.BookToPDF.desc=Конвертує формати книги/комікса у PDF за допомогою calibre
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
########################### ###########################
# # # #
# WEB PAGES # # WEB PAGES #
@ -436,6 +440,8 @@ login.rememberme=Запам'ятати мене
login.invalid=Недійсне ім'я користувача або пароль. login.invalid=Недійсне ім'я користувача або пароль.
login.locked=Ваш обліковий запис заблоковано. login.locked=Ваш обліковий запис заблоковано.
login.signinTitle=Будь ласка, увійдіть login.signinTitle=Будь ласка, увійдіть
login.ssoSignIn=Увійти через єдиний вхід
login.oauth2AutoCreateDisabled=Автоматичне створення користувача OAUTH2 ВИМКНЕНО
#auto-redact #auto-redact
@ -606,6 +612,7 @@ pageLayout.pagesPerSheet=Сторінок на одному аркуші:
pageLayout.addBorder=Додати рамки pageLayout.addBorder=Додати рамки
pageLayout.submit=Відправити pageLayout.submit=Відправити
#scalePages #scalePages
scalePages.title=Відрегулювати масштаб сторінки scalePages.title=Відрегулювати масштаб сторінки
scalePages.header=Відрегулювати масштаб сторінки scalePages.header=Відрегулювати масштаб сторінки
@ -687,6 +694,7 @@ repair.submit=Ремонтувати
#flatten #flatten
flatten.title=Згладжування flatten.title=Згладжування
flatten.header=Згладжування PDF flatten.header=Згладжування PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=Згладити flatten.submit=Згладити
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF в PDF/A
pdfToPDFA.credit=Цей сервіс використовує OCRmyPDF для перетворення у формат PDF/A pdfToPDFA.credit=Цей сервіс використовує OCRmyPDF для перетворення у формат PDF/A
pdfToPDFA.submit=Конвертувати pdfToPDFA.submit=Конвертувати
pdfToPDFA.tip=Наразі не працює для кількох вхідних файлів одночасно pdfToPDFA.tip=Наразі не працює для кількох вхідних файлів одночасно
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=Введіть кількість вер
split-by-sections.submit=Розділити PDF split-by-sections.submit=Розділити PDF
split-by-sections.merge=Об'єднати в один 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
licenses.nav=Ліцензії licenses.nav=Ліцензії
licenses.title=Ліцензії від третіх сторін licenses.title=Ліцензії від третіх сторін

View File

@ -57,6 +57,8 @@ usernameExistsMessage=新用户名已存在。
invalidUsernameMessage=用户名无效,用户名只能由字母字符和数字组成。 invalidUsernameMessage=用户名无效,用户名只能由字母字符和数字组成。
deleteCurrentUserMessage=无法删除当前登录的用户。 deleteCurrentUserMessage=无法删除当前登录的用户。
deleteUsernameExistsMessage=用户名不存在,无法删除。 deleteUsernameExistsMessage=用户名不存在,无法删除。
downgradeCurrentUserMessage=无法降级当前用户的角色
downgradeCurrentUserLongMessage=无法降级当前用户的角色。因此,当前用户将不会显示。
error=错误 error=错误
oops=哎呀! oops=哎呀!
help=帮助 help=帮助
@ -112,6 +114,7 @@ navbar.settings=设置
############# #############
settings.title=设置 settings.title=设置
settings.update=可更新 settings.update=可更新
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=应用程序版本: settings.appVersion=应用程序版本:
settings.downloadOption.title=选择下载选项(单个文件非压缩文件): settings.downloadOption.title=选择下载选项(单个文件非压缩文件):
settings.downloadOption.1=在同一窗口打开 settings.downloadOption.1=在同一窗口打开
@ -120,8 +123,9 @@ settings.downloadOption.3=下载文件
settings.zipThreshold=当下载的文件数量超过限制时,将文件压缩。 settings.zipThreshold=当下载的文件数量超过限制时,将文件压缩。
settings.signOut=登出 settings.signOut=登出
settings.accountSettings=帐号设定 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.title=更改凭证
changeCreds.header=更新您的账户详情 changeCreds.header=更新您的账户详情
@ -171,6 +175,7 @@ adminUserSettings.demoUser=演示用户(无自定义设置)
adminUserSettings.internalApiUser=内部API用户 adminUserSettings.internalApiUser=内部API用户
adminUserSettings.forceChange=强制用户在登录时更改用户名/密码 adminUserSettings.forceChange=强制用户在登录时更改用户名/密码
adminUserSettings.submit=保存用户 adminUserSettings.submit=保存用户
adminUserSettings.changeUserRole=更改用户角色
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=记住我
login.invalid=用户名或密码无效。 login.invalid=用户名或密码无效。
login.locked=您的账户已被锁定。 login.locked=您的账户已被锁定。
login.signinTitle=请登录 login.signinTitle=请登录
login.ssoSignIn=通过单点登录登录
login.oauth2AutoCreateDisabled=OAUTH2自动创建用户已禁用
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=修复
#flatten #flatten
flatten.title=展平 flatten.title=展平
flatten.header=展平 PDF flatten.header=展平 PDF
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=展平 flatten.submit=展平
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF转换为PDF/A
pdfToPDFA.credit=此服务使用OCRmyPDF进行PDF/A转换 pdfToPDFA.credit=此服务使用OCRmyPDF进行PDF/A转换
pdfToPDFA.submit=转换 pdfToPDFA.submit=转换
pdfToPDFA.tip=目前不支持上传多个 pdfToPDFA.tip=目前不支持上传多个
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=输入垂直分割数
split-by-sections.submit=分割PDF split-by-sections.submit=分割PDF
split-by-sections.merge=是否合并为一个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
licenses.nav=许可证 licenses.nav=许可证
licenses.title=第三方许可证 licenses.title=第三方许可证

View File

@ -57,6 +57,8 @@ usernameExistsMessage=新使用者名稱已存在。
invalidUsernameMessage=使用者名無效,使用者名只能包含字母字元和數位。 invalidUsernameMessage=使用者名無效,使用者名只能包含字母字元和數位。
deleteCurrentUserMessage=無法刪除目前登錄的使用者。 deleteCurrentUserMessage=無法刪除目前登錄的使用者。
deleteUsernameExistsMessage=使用者名不存在,無法刪除。 deleteUsernameExistsMessage=使用者名不存在,無法刪除。
downgradeCurrentUserMessage=無法降級目前使用者的角色
downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,不會顯示目前的使用者。
error=錯誤 error=錯誤
oops=哎呀! oops=哎呀!
help=幫助 help=幫助
@ -112,6 +114,7 @@ navbar.settings=設定
############# #############
settings.title=設定 settings.title=設定
settings.update=有更新可用 settings.update=有更新可用
settings.updateAvailable={0} is the current installed version. A new version ({1}) is available.
settings.appVersion=應用版本: settings.appVersion=應用版本:
settings.downloadOption.title=選擇下載選項(對於單一檔案非壓縮下載): settings.downloadOption.title=選擇下載選項(對於單一檔案非壓縮下載):
settings.downloadOption.1=在同一視窗中開啟 settings.downloadOption.1=在同一視窗中開啟
@ -120,8 +123,9 @@ settings.downloadOption.3=下載檔案
settings.zipThreshold=當下載的檔案數量超過時,壓縮檔案 settings.zipThreshold=當下載的檔案數量超過時,壓縮檔案
settings.signOut=登出 settings.signOut=登出
settings.accountSettings=帳戶設定 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.title=變更憑證
changeCreds.header=更新您的帳戶詳細資訊 changeCreds.header=更新您的帳戶詳細資訊
@ -171,6 +175,7 @@ adminUserSettings.demoUser=示範用途的使用者(無自訂設定)
adminUserSettings.internalApiUser=內部 API 使用者 adminUserSettings.internalApiUser=內部 API 使用者
adminUserSettings.forceChange=強制使用者在登入時修改使用者名稱/密碼 adminUserSettings.forceChange=強制使用者在登入時修改使用者名稱/密碼
adminUserSettings.submit=儲存 adminUserSettings.submit=儲存
adminUserSettings.changeUserRole=更改使用者身份
############# #############
# HOME-PAGE # # HOME-PAGE #
@ -435,6 +440,8 @@ login.rememberme=記住我
login.invalid=使用者名稱或密碼無效。 login.invalid=使用者名稱或密碼無效。
login.locked=您的帳戶已被鎖定。 login.locked=您的帳戶已被鎖定。
login.signinTitle=請登入 login.signinTitle=請登入
login.ssoSignIn=透過織網單一簽入
login.oauth2AutoCreateDisabled=OAUTH2自動建立使用者已停用
#auto-redact #auto-redact
@ -687,6 +694,7 @@ repair.submit=修復
#flatten #flatten
flatten.title=平坦化 flatten.title=平坦化
flatten.header=PDF 平坦化 flatten.header=PDF 平坦化
flatten.flattenOnlyForms=Flatten only forms
flatten.submit=平坦化 flatten.submit=平坦化
@ -939,6 +947,7 @@ pdfToPDFA.header=PDF 轉 PDF/A
pdfToPDFA.credit=此服務使用 OCRmyPDF 進行 PDF/A 轉換 pdfToPDFA.credit=此服務使用 OCRmyPDF 進行 PDF/A 轉換
pdfToPDFA.submit=轉換 pdfToPDFA.submit=轉換
pdfToPDFA.tip=目前不支援上傳多個 pdfToPDFA.tip=目前不支援上傳多個
pdfToPDFA.outputFormat=Output format
#PDFToWord #PDFToWord
@ -1022,6 +1031,15 @@ split-by-sections.vertical.placeholder=輸入垂直劃分的數量
split-by-sections.submit=分割 PDF split-by-sections.submit=分割 PDF
split-by-sections.merge=是否合併為一個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
licenses.nav=許可證 licenses.nav=許可證
licenses.title=第三方許可證 licenses.title=第三方許可證

View File

@ -7,16 +7,24 @@ security:
csrfDisabled: true csrfDisabled: true
loginAttemptCount: 5 # lock user account after 5 tries loginAttemptCount: 5 # lock user account after 5 tries
loginResetTimeMinutes : 120 # lock account for 2 hours after x attempts 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: system:
defaultLocale: 'en-US' # Set the default language (e.g. 'de-DE', 'fr-FR', etc) 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 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) 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: ui:
# appName: exampleAppName # Application's visible name appName: null # Application's visible name
# homeDescription: I am a description # Short description or tagline shown on homepage. homeDescription: null # Short description or tagline shown on homepage.
# appNameNavbar: navbarName # Name displayed on the navigation bar appNameNavbar: null # Name displayed on the navigation bar
endpoints: endpoints:
toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages']) toRemove: [] # List endpoints to disable (e.g. ['img-to-pdf', 'remove-pages'])
@ -24,3 +32,7 @@ endpoints:
metrics: metrics:
enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable enabled: true # 'true' to enable Info APIs (`/api/*`) endpoints, 'false' to disable
# Automatically Generated Settings (Do Not Edit Directly)
AutomaticallyGenerated:
key: example

View File

@ -81,6 +81,13 @@
"moduleLicense": "GNU Lesser General Public License v3 (LGPL-v3)", "moduleLicense": "GNU Lesser General Public License v3 (LGPL-v3)",
"moduleLicenseUrl": "http://www.gnu.org/licenses/lgpl-3.0.html" "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", "moduleName": "com.github.vladimir-bukhtoyarov:bucket4j-core",
"moduleUrl": "http://github.com/vladimir-bukhtoyarov/bucket4j/bucket4j-core", "moduleUrl": "http://github.com/vladimir-bukhtoyarov/bucket4j/bucket4j-core",
@ -109,6 +116,34 @@
"moduleLicense": "LGPL", "moduleLicense": "LGPL",
"moduleLicenseUrl": "http://www.martiansoftware.com/jsap/license.html" "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", "moduleName": "com.opencsv:opencsv",
"moduleUrl": "http://opencsv.sf.net", "moduleUrl": "http://opencsv.sf.net",
@ -335,6 +370,20 @@
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt" "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", "moduleName": "org.antlr:antlr4-runtime",
"moduleUrl": "https://www.antlr.org/", "moduleUrl": "https://www.antlr.org/",
@ -547,6 +596,13 @@
"moduleLicense": "Public Domain, per Creative Commons CC0", "moduleLicense": "Public Domain, per Creative Commons CC0",
"moduleLicenseUrl": "http://creativecommons.org/publicdomain/zero/1.0/" "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", "moduleName": "org.slf4j:jul-to-slf4j",
"moduleUrl": "http://www.slf4j.org", "moduleUrl": "http://www.slf4j.org",
@ -663,6 +719,13 @@
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-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", "moduleName": "org.springframework.boot:spring-boot-starter-security",
"moduleUrl": "https://spring.io/projects/spring-boot", "moduleUrl": "https://spring.io/projects/spring-boot",
@ -726,6 +789,27 @@
"moduleLicense": "Apache License, Version 2.0", "moduleLicense": "Apache License, Version 2.0",
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-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", "moduleName": "org.springframework.security:spring-security-web",
"moduleUrl": "https://spring.io/projects/spring-security", "moduleUrl": "https://spring.io/projects/spring-security",

View File

@ -89,3 +89,38 @@
.jumbotron { .jumbotron {
padding: 3rem 3rem; /* Reduce vertical padding */ 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;
}
}

View 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

View 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);
});
});

View File

@ -5,6 +5,7 @@ const DraggableUtils = {
pdfDoc: null, pdfDoc: null,
pageIndex: 0, pageIndex: 0,
documentsMap: new Map(), documentsMap: new Map(),
lastInteracted: null,
init() { init() {
interact(".draggable-canvas") interact(".draggable-canvas")
@ -12,14 +13,18 @@ const DraggableUtils = {
listeners: { listeners: {
move: (event) => { move: (event) => {
const target = event.target; const target = event.target;
const x = (parseFloat(target.getAttribute("data-bs-x")) || 0) + event.dx; const x = (parseFloat(target.getAttribute("data-bs-x")) || 0)
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0) + event.dy; + event.dx;
const y = (parseFloat(target.getAttribute("data-bs-y")) || 0)
+ event.dy;
target.style.transform = `translate(${x}px, ${y}px)`; target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute("data-bs-x", x); target.setAttribute("data-bs-x", x);
target.setAttribute("data-bs-y", y); target.setAttribute("data-bs-y", y);
this.onInteraction(target); this.onInteraction(target);
//update the last interacted element
this.lastInteracted = event.target;
}, },
}, },
}) })
@ -38,7 +43,8 @@ const DraggableUtils = {
let width = event.rect.width; let width = event.rect.width;
let height = event.rect.height; let height = event.rect.height;
if (Math.abs(event.deltaRect.width) >= Math.abs(event.deltaRect.height)) { if (Math.abs(event.deltaRect.width) >= Math.abs(
event.deltaRect.height)) {
height = width / aspectRatio; height = width / aspectRatio;
} else { } else {
width = height * aspectRatio; width = height * aspectRatio;
@ -59,7 +65,8 @@ const DraggableUtils = {
target.setAttribute("data-bs-x", x); target.setAttribute("data-bs-x", x);
target.setAttribute("data-bs-y", y); target.setAttribute("data-bs-y", y);
target.textContent = Math.round(event.rect.width) + "\u00D7" + Math.round(event.rect.height); target.textContent = Math.round(event.rect.width) + "\u00D7"
+ Math.round(event.rect.height);
this.onInteraction(target); this.onInteraction(target);
}, },
@ -72,7 +79,56 @@ const DraggableUtils = {
], ],
inertia: true, 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) { onInteraction(target) {
this.boxDragContainer.appendChild(target); this.boxDragContainer.appendChild(target);
}, },
@ -88,9 +144,18 @@ const DraggableUtils = {
createdCanvas.setAttribute("data-bs-x", x); createdCanvas.setAttribute("data-bs-x", x);
createdCanvas.setAttribute("data-bs-y", y); 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); createdCanvas.onclick = (e) => this.onInteraction(e.target);
this.boxDragContainer.appendChild(createdCanvas); this.boxDragContainer.appendChild(createdCanvas);
//Enable Arrow keys directly after the element is created
this.lastInteracted = createdCanvas;
return createdCanvas; return createdCanvas;
}, },
createDraggableCanvasFromUrl(dataUrl) { createDraggableCanvasFromUrl(dataUrl) {
@ -134,6 +199,11 @@ const DraggableUtils = {
}, },
deleteDraggableCanvas(element) { deleteDraggableCanvas(element) {
if (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(); element.remove();
} }
}, },

View File

@ -104,4 +104,9 @@ function setupFileInput(chooser) {
$(inputElement).siblings(".custom-file-label").addClass("selected").html(pdfPrompt); $(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);
});
} }

View File

@ -30,19 +30,39 @@ async function getLatestReleaseVersion() {
async function checkForUpdate() { async function checkForUpdate() {
// Initialize the update button as hidden // 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) { if (updateBtn !== null) {
updateBtn.style.display = "none"; 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(); const latestVersion = await getLatestReleaseVersion();
console.log("latestVersion=" + latestVersion); console.log("latestVersion=" + latestVersion);
console.log("currentVersion=" + currentVersion); console.log("currentVersion=" + currentVersion);
console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion)); console.log("compareVersions(latestVersion, currentVersion) > 0)=" + compareVersions(latestVersion, currentVersion));
if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) { if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
if (updateBtn != null) {
document.getElementById("update-btn").style.display = "block"; 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"); console.log("visible");
} else { } else {
if (updateLink !== null) {
if (!updateLink.classList.contains("visually-hidden")) {
updateLink.classList.add("visually-hidden");
}
}
console.log("hidden"); console.log("hidden");
} }
} }

View File

@ -46,6 +46,12 @@ function reorderCards() {
cards.sort(function (a, b) { cards.sort(function (a, b) {
var aIsFavorite = localStorage.getItem(a.id) === "favorite"; var aIsFavorite = localStorage.getItem(a.id) === "favorite";
var bIsFavorite = localStorage.getItem(b.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) { if (aIsFavorite && !bIsFavorite) {
return -1; return -1;
} }

View File

@ -69,8 +69,13 @@ function attachMoveButtons() {
removeButtons[i].addEventListener("click", function (event) { removeButtons[i].addEventListener("click", function (event) {
event.preventDefault(); event.preventDefault();
var parent = this.closest(".list-group-item"); var parent = this.closest(".list-group-item");
//Get name of removed file
var fileName = parent.querySelector(".filename").innerText;
parent.remove(); parent.remove();
updateFiles(); updateFiles();
//Dispatch a custom event with the name of the removed file
var event = new CustomEvent("fileRemoved", { detail: fileName });
document.dispatchEvent(event);
}); });
} }
} }

View File

@ -31,3 +31,12 @@ document.getElementById("boredWaiting").addEventListener("change", function () {
boredWaiting = this.checked ? "enabled" : "disabled"; boredWaiting = this.checked ? "enabled" : "disabled";
localStorage.setItem("boredWaiting", boredWaiting); 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);
});

View File

@ -42,7 +42,7 @@
</div> </div>
</th:block> </th:block>
<!-- Change Username Form --> <!-- Change Username Form -->
<form action="api/v1/user/change-username" method="post"> <form th:if="${!oAuth2Login}" action="api/v1/user/change-username" method="post">
<div class="mb-3"> <div class="mb-3">
<label for="newUsername" th:text="#{account.changeUsername}">Change Username</label> <label for="newUsername" th:text="#{account.changeUsername}">Change Username</label>
<input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}"> <input type="text" class="form-control" name="newUsername" id="newUsername" th:placeholder="#{account.newUsername}">
@ -59,8 +59,8 @@
<hr> <!-- Separator Line --> <hr> <!-- Separator Line -->
<!-- Change Password Form --> <!-- Change Password Form -->
<h4 th:text="#{account.changePassword}">Change Password?</h4> <h4 th:if="${!oAuth2Login}" th:text="#{account.changePassword}">Change Password?</h4>
<form action="api/v1/user/change-password" method="post"> <form th:if="${!oAuth2Login}" action="api/v1/user/change-password" method="post">
<div class="mb-3"> <div class="mb-3">
<label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label> <label for="currentPassword" th:text="#{account.oldPassword}">Old Password</label>
<input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword" th:placeholder="#{account.oldPassword}"> <input type="password" class="form-control" name="currentPassword" id="currentPasswordPassword" th:placeholder="#{account.oldPassword}">

View File

@ -58,6 +58,7 @@
<div class="mb-3"> <div class="mb-3">
<label for="role" th:text="#{adminUserSettings.role}">Role</label> <label for="role" th:text="#{adminUserSettings.role}">Role</label>
<select name="role" class="form-control" required> <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> <option th:each="roleDetail : ${roleDetails}" th:value="${roleDetail.key}" th:text="#{${roleDetail.value}}">Role</option>
</select> </select>
</div> </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> <label class="form-check-label" for="forceChange" th:text="#{adminUserSettings.forceChange}">Force user to change username/password on login</label>
</div> </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 --> <!-- Add other fields as required -->
<button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button> <button type="submit" class="btn btn-primary" th:text="#{adminUserSettings.submit}">Save User</button>
</form> </form>
@ -73,6 +100,11 @@
</div> </div>
</div> </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> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
</body> </body>

View File

@ -17,6 +17,13 @@
<p th:text="#{pdfToPDFA.tip}"></p> <p th:text="#{pdfToPDFA.tip}"></p>
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/convert/pdf/pdfa}"> <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 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> <br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToPDFA.submit}"></button> <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{pdfToPDFA.submit}"></button>
</form> </form>

View File

@ -19,14 +19,13 @@
<div class="mb-3"> <div class="mb-3">
<label th:text="#{PDFToText.selectText.1}"></label> <label th:text="#{PDFToText.selectText.1}"></label>
<select class="form-control" name="outputFormat"> <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> <option value="txt">TXT</option>
</select> </select>
</div> </div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button> <button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{PDFToText.submit}"></button>
</form> </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> </div>
</div> </div>

View File

@ -10,12 +10,12 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<!-- Icons --> <!-- Icons -->
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png?v=2"> <link rel="apple-touch-icon" sizes="180x180" href="apple-touch-icon.png?v=2">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png?v=2"> <link rel="icon" type="image/png" sizes="32x32" href="favicon-32x32.png?v=2">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png?v=2"> <link rel="icon" type="image/png" sizes="16x16" href="favicon-16x16.png?v=2">
<link rel="manifest" href="/site.webmanifest?v=2"> <link rel="manifest" href="site.webmanifest?v=2">
<link rel="mask-icon" href="/safari-pinned-tab.svg?v=2" color="#ca2b2a"> <link rel="mask-icon" href="safari-pinned-tab.svg?v=2" color="#ca2b2a">
<link rel="shortcut icon" href="/favicon.ico?v=2"> <link rel="shortcut icon" href="favicon.ico?v=2">
<meta name="apple-mobile-web-app-title" content="Stirling PDF"> <meta name="apple-mobile-web-app-title" content="Stirling PDF">
<meta name="application-name" content="Stirling PDF"> <meta name="application-name" content="Stirling PDF">
<meta name="msapplication-TileColor" content="#00aba9"> <meta name="msapplication-TileColor" content="#00aba9">
@ -60,7 +60,7 @@
<!-- Help Modal --> <!-- Help Modal -->
<link rel="stylesheet" href="css/errorBanner.css"> <link rel="stylesheet" href="css/errorBanner.css">
<script src="js/cacheFormInputs.js"></script>
<script src="js/tab-container.js"></script> <script src="js/tab-container.js"></script>
<script src="js/darkmode.js"></script> <script src="js/darkmode.js"></script>
</th:block> </th:block>

View File

@ -3,6 +3,7 @@
<script th:inline="javascript"> <script th:inline="javascript">
const currentVersion = /*[[${@appVersion}]]*/ ''; const currentVersion = /*[[${@appVersion}]]*/ '';
const noFavourites = /*[[#{noFavourites}]]*/ ''; const noFavourites = /*[[#{noFavourites}]]*/ '';
const updateAvailable = /*[[#{settings.updateAvailable}]]*/ '';
</script> </script>
<script th:src="@{js/githubVersion.js}"></script> <script th:src="@{js/githubVersion.js}"></script>
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
@ -16,20 +17,20 @@
</button> </button>
<div class="collapse navbar-collapse" id="navbarNav"> <div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto flex-nowrap"> <ul class="navbar-nav me-auto flex-nowrap">
<li class="nav-item"> <li class="nav-item" th:if="${@endpointConfiguration.isEndpointEnabled('multi-tool')}">
<a class="nav-link" href="#" th:href="@{multi-tool}" th:classappend="${currentPage}=='multi-tool' ? 'active' : ''" th:title="#{home.multiTool.desc}"> <a class="nav-link" href="#" th:href="@{multi-tool}" th:classappend="${currentPage}=='multi-tool' ? 'active' : ''" th:title="#{home.multiTool.desc}">
<img class="icon" src="images/tools.svg" alt="icon"> <img class="icon" src="images/tools.svg" alt="icon">
<span class="icon-text" th:text="#{home.multiTool.title}"></span> <span class="icon-text" th:text="#{home.multiTool.title}"></span>
</a> </a>
</li> </li>
<li class="nav-item nav-item-separator"></li> <li th:if="${@endpointConfiguration.isEndpointEnabled('multi-tool')}" class="nav-item nav-item-separator"></li>
<li class="nav-item"> <li th:if="${@endpointConfiguration.isEndpointEnabled('pipeline')}" class="nav-item">
<a class="nav-link" href="#" th:href="@{pipeline}" th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}"> <a class="nav-link" href="#" th:href="@{pipeline}" th:classappend="${currentPage}=='pipeline' ? 'active' : ''" th:title="#{home.pipeline.desc}">
<img class="icon" src="images/pipeline.svg" alt="icon"> <img class="icon" src="images/pipeline.svg" alt="icon">
<span class="icon-text" th:text="#{home.pipeline.title}"></span> <span class="icon-text" th:text="#{home.pipeline.title}"></span>
</a> </a>
</li> </li>
<li class="nav-item nav-item-separator"></li> <li th:if="${@endpointConfiguration.isEndpointEnabled('pipeline')}" class="nav-item nav-item-separator"></li>
<li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='crop' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' OR ${currentPage}=='auto-split-pdf' OR ${currentPage}=='extract-page' OR ${currentPage}=='pdf-to-single-page' ? 'active' : ''"> <li class="nav-item dropdown" th:classappend="${currentPage}=='remove-pages' OR ${currentPage}=='merge-pdfs' OR ${currentPage}=='split-pdfs' OR ${currentPage}=='crop' OR ${currentPage}=='adjust-contrast' OR ${currentPage}=='pdf-organizer' OR ${currentPage}=='rotate-pdf' OR ${currentPage}=='multi-page-layout' OR ${currentPage}=='scale-pages' OR ${currentPage}=='auto-split-pdf' OR ${currentPage}=='extract-page' OR ${currentPage}=='pdf-to-single-page' ? 'active' : ''">
<a class="nav-link dropdown-toggle" id="navbarDropdown-1" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="nav-link dropdown-toggle" id="navbarDropdown-1" href="#" role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<img class="icon" src="images/file-earmark-pdf.svg" alt="icon"> <img class="icon" src="images/file-earmark-pdf.svg" alt="icon">
@ -194,7 +195,7 @@
<p class="mb-0" th:utext="#{settings.appVersion} + ' ' + ${@appVersion}"></p> <p class="mb-0" th:utext="#{settings.appVersion} + ' ' + ${@appVersion}"></p>
<a href="https://github.com/sponsors/Frooodle" class="btn btn-sm btn-outline-primary" role="button" target="_blank" th:text="#{sponsor}+' Stirling-PDF'"></a> <a href="https://github.com/sponsors/Frooodle" class="btn btn-sm btn-outline-primary" role="button" target="_blank" th:text="#{sponsor}+' Stirling-PDF'"></a>
<a href="swagger-ui/index.html" class="btn btn-sm btn-outline-primary" role="button" target="_blank">API</a> <a href="swagger-ui/index.html" class="btn btn-sm btn-outline-primary" role="button" target="_blank">API</a>
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases" class="btn btn-sm btn-outline-primary" id="update-btn" th:utext="#{settings.update}" role="button" target="_blank"></a> <a th:if="${@shouldShow}" href="https://github.com/Stirling-Tools/Stirling-PDF/releases" class="btn btn-sm btn-outline-primary" id="update-btn" th:utext="#{settings.update}" role="button" target="_blank" rel="noopener"></a>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label> <label for="downloadOption" th:utext="#{settings.downloadOption.title}"></label>
@ -210,13 +211,17 @@
<span id="zipThresholdValue" class="ms-2"></span> <span id="zipThresholdValue" class="ms-2"></span>
</div> </div>
<div class="mb-3 form-check"> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="boredWaiting"> <input type="checkbox" class="form-check-input" id="boredWaiting" th:title="#{settings.bored.help}">
<label class="form-check-label" for="boredWaiting" th:text="#{bored}"></label> <label class="form-check-label" for="boredWaiting" th:text="#{bored}"></label>
</div> </div>
<a th:if="${@loginEnabled}" href="account" class="btn btn-sm btn-outline-primary" role="button" th:text="#{settings.accountSettings}" target="_blank">Account Settings</a> <div class="mb-3 form-check">
<input type="checkbox" class="form-check-input" id="cacheInputs" th:title="#{settings.cacheInputs.help}">
<label class="form-check-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>
<div class="modal-footer"> <div class="modal-footer">
<a th:if="${@loginEnabled}" class="btn btn-danger" role="button" th:text="#{settings.signOut}" href="logout">Sign Out</a> <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> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
</div> </div>
</div> </div>

View File

@ -23,6 +23,15 @@
<br> <br>
<input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}" autofocus> <input type="text" id="searchBar" onkeyup="filterCards()" th:placeholder="#{home.searchBar}" autofocus>
<div class="features-container"> <div class="features-container">
<div th:if="${@shouldShow}" class="feature-card favorite" id="update-link" style="display: none;">
<a href="https://github.com/Stirling-Tools/Stirling-PDF/releases" target="_blank" class="nav-link text-body-secondary px-2" rel="noopener">
<div class="d-flex align-items-center">
<img class="card-icon home-card-icon home-card-icon-colour" src="images/update.svg" alt="Icon" width="30" height="30">
<h5 class="card-title lookatme ms-2" th:text="#{settings.update}" th:data-lookatme-text="#{settings.update}"></h5>
</div>
<p class="card-text" id="app-update"></p>
</a>
</div>
<div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg', tags=#{pipeline.tags})}"></div> <div th:replace="~{fragments/card :: card(id='pipeline', cardTitle=#{home.pipeline.title}, cardText=#{home.pipeline.desc}, cardLink='pipeline', svgPath='images/pipeline.svg', tags=#{pipeline.tags})}"></div>
<div th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', svgPath='images/book-opened.svg', tags=#{viewPdf.tags})}"></div> <div th:replace="~{fragments/card :: card(id='view-pdf', cardTitle=#{home.viewPdf.title}, cardText=#{home.viewPdf.desc}, cardLink='view-pdf', svgPath='images/book-opened.svg', tags=#{viewPdf.tags})}"></div>

View File

@ -52,7 +52,7 @@
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
const defaultLocale = document.documentElement.getAttribute('language') || 'en_GB'; const defaultLocale = document.documentElement.getAttribute('data-language') || 'en_GB';
const storedLocale = localStorage.getItem('languageCode') || defaultLocale; const storedLocale = localStorage.getItem('languageCode') || defaultLocale;
const currentURL = new URL(window.location.href); const currentURL = new URL(window.location.href);
@ -139,6 +139,13 @@
<form th:action="@{login}" method="post"> <form th:action="@{login}" method="post">
<img class="mb-4" src="favicon.svg?v=2" alt="" width="144" height="144"> <img class="mb-4" src="favicon.svg?v=2" alt="" width="144" height="144">
<h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1> <h1 class="h1 mb-3 fw-normal" th:text="${@appName}">Stirling-PDF</h1>
<div th:if="${oAuth2Enabled}">
<a href="oauth2/authorization/oidc" class="w-100 btn btn-lg btn-primary" th:text="#{login.ssoSignIn}">Login Via SSO</a>
<div class="text-danger text-center">
<div th:if="${error == 'oauth2AutoCreateDisabled'}" th:text="#{login.oauth2AutoCreateDisabled}">OAUTH2 Auto-Create User Disabled.</div>
</div>
<hr />
</div>
<h2 class="h5 mb-3 fw-normal" th:text="#{login.signinTitle}">Please sign in</h2> <h2 class="h5 mb-3 fw-normal" th:text="#{login.signinTitle}">Please sign in</h2>
<div class="form-floating"> <div class="form-floating">

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="http://www.thymeleaf.org">
<head>
<th:block th:insert="~{fragments/common :: head(title=#{fakeScan.title}, header=#{fakeScan.header})}"></th:block>
</head>
<body>
<th:block th:insert="~{fragments/common :: game}"></th:block>
<div id="page-container">
<div id="content-wrap">
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
<br><br>
<div class="container">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 th:text="#{fakeScan.header}"></h2>
<form method="post" enctype="multipart/form-data" th:action="@{api/v1/misc/fake-scan}">
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multiple=false, accept='application/pdf')}"></div>
<br>
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{fakeScan.submit}"></button>
</form>
</div>
</div>
</div>
</div>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More