Compare commits
31 Commits
v0.30.1
...
Frooodle/l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a8709c25c0 | ||
|
|
261e69f3ca | ||
|
|
addb999301 | ||
|
|
496fcad698 | ||
|
|
daf4f49050 | ||
|
|
2cdad2f9ff | ||
|
|
4c4613cfec | ||
|
|
8aa0e85c20 | ||
|
|
1129487aaf | ||
|
|
378d0f8afb | ||
|
|
a12de218c0 | ||
|
|
1ba271b0f4 | ||
|
|
6ccc29d0a7 | ||
|
|
eee2f5c666 | ||
|
|
e660237e28 | ||
|
|
83e93688ee | ||
|
|
dedfabd630 | ||
|
|
3b5b7772a9 | ||
|
|
c59d3ff3e0 | ||
|
|
5832147b30 | ||
|
|
20dc2f60cd | ||
|
|
b46ccdde44 | ||
|
|
4fa1b4adb0 | ||
|
|
03158b05e4 | ||
|
|
28c55ca80c | ||
|
|
6ca14edaf1 | ||
|
|
f9677b1fe8 | ||
|
|
04a6ebf515 | ||
|
|
262e2ed47a | ||
|
|
4436759e12 | ||
|
|
87925ac618 |
@@ -29,7 +29,7 @@ If you would like to add or modify a translation, please see [How to add new lan
|
|||||||
|
|
||||||
## Docs
|
## Docs
|
||||||
|
|
||||||
Documentation for Stirling-PDF is handled in a separate 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://docs.stirlingpdf.com/](https://docs.stirlingpdf.com/).
|
Documentation for Stirling-PDF is handled in a separate 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
|
||||||
|
|
||||||
@@ -41,4 +41,4 @@ If, at any point of time, you have a question, please feel free to ask in the sa
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
By contributing to this project, you agree that your contributions will be licensed under the [MIT License](LICENSE).
|
By contributing to this project, you agree that your contributions will be licensed under the [GPL 3 License](LICENSE). You also acknowledge and agree that your contributions will be included in Stirling-PDF and that they can be relicensed in the future under the MPL 2.0 (Mozilla Public License Version 2.0) license.
|
||||||
|
|||||||
@@ -1,557 +0,0 @@
|
|||||||
# Stirling-PDF Developer Guide
|
|
||||||
|
|
||||||
## 1. Introduction
|
|
||||||
|
|
||||||
Stirling-PDF is a robust, locally hosted web-based PDF manipulation tool. This guide focuses on Docker-based development and testing, which is the recommended approach for working with the full version of Stirling-PDF.
|
|
||||||
|
|
||||||
## 2. Project Overview
|
|
||||||
|
|
||||||
Stirling-PDF is built using:
|
|
||||||
- Spring Boot + Thymeleaf
|
|
||||||
- PDFBox
|
|
||||||
- LibreOffice
|
|
||||||
- OcrMyPdf
|
|
||||||
- HTML, CSS, JavaScript
|
|
||||||
- Docker
|
|
||||||
- PDF.js
|
|
||||||
- PDF-LIB.js
|
|
||||||
- Lombok
|
|
||||||
|
|
||||||
## 3. Development Environment Setup
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
- Docker
|
|
||||||
- Git
|
|
||||||
- Java JDK 17 or later
|
|
||||||
- Gradle 7.0 or later (Included within repo)
|
|
||||||
|
|
||||||
### Setup Steps
|
|
||||||
1. Clone the repository:
|
|
||||||
```
|
|
||||||
git clone https://github.com/Stirling-Tools/Stirling-PDF.git
|
|
||||||
cd Stirling-PDF
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install Docker and JDK17 if not already installed.
|
|
||||||
|
|
||||||
3. Install a recommended Java IDE such as Eclipse, IntelliJ or VSCode
|
|
||||||
|
|
||||||
4. Lombok Setup
|
|
||||||
Stirling-PDF uses Lombok to reduce boilerplate code. Some IDEs, like Eclipse, don't support Lombok out of the box. To set up Lombok in your development environment:
|
|
||||||
Visit the [Lombok website](https://projectlombok.org/setup/) for installation instructions specific to your IDE.
|
|
||||||
|
|
||||||
5. Add environment variable
|
|
||||||
For local testing you should generally be testing the full 'Security' version of Stirling-PDF to do this you must add the environment flag DOCKER_ENABLE_SECURITY=true to your system and/or IDE build/run step
|
|
||||||
|
|
||||||
|
|
||||||
## 4. Project Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
Stirling-PDF/
|
|
||||||
├── .github/ # GitHub-specific files (workflows, issue templates)
|
|
||||||
├── configs/ # Configuration files used by stirling at runtime (generated at runtime)
|
|
||||||
├── cucumber/ # Cucumber test files
|
|
||||||
│ ├── features/
|
|
||||||
├── customFiles/ # Custom static files and templates (generated at runtime used to replace existing files)
|
|
||||||
├── docs/ # Documentation files
|
|
||||||
├── exampleYmlFiles/ # Example YAML configuration files
|
|
||||||
├── images/ # Image assets
|
|
||||||
├── pipeline/ # Pipeline-related files (generated at runtime)
|
|
||||||
├── scripts/ # Utility scripts
|
|
||||||
├── src/ # Source code
|
|
||||||
│ ├── main/
|
|
||||||
│ │ ├── java/
|
|
||||||
│ │ │ └── stirling/
|
|
||||||
│ │ │ └── software/
|
|
||||||
│ │ │ └── SPDF/
|
|
||||||
│ │ │ ├── config/
|
|
||||||
│ │ │ ├── controller/
|
|
||||||
│ │ │ ├── model/
|
|
||||||
│ │ │ ├── repository/
|
|
||||||
│ │ │ ├── service/
|
|
||||||
│ │ │ └── utils/
|
|
||||||
│ │ └── resources/
|
|
||||||
│ │ ├── static/
|
|
||||||
│ │ │ ├── css/
|
|
||||||
│ │ │ ├── js/
|
|
||||||
│ │ │ └── pdfjs/
|
|
||||||
│ │ └── templates/
|
|
||||||
│ └── test/
|
|
||||||
│ └── java/
|
|
||||||
│ └── stirling/
|
|
||||||
│ └── software/
|
|
||||||
│ └── SPDF/
|
|
||||||
├── build.gradle # Gradle build configuration
|
|
||||||
├── Dockerfile # Main Dockerfile
|
|
||||||
├── Dockerfile-ultra-lite # Dockerfile for ultra-lite version
|
|
||||||
├── Dockerfile-fat # Dockerfile for fat version
|
|
||||||
├── docker-compose.yml # Docker Compose configuration
|
|
||||||
└── test.sh # Test script to deploy all docker versions and run cuke tests
|
|
||||||
```
|
|
||||||
|
|
||||||
## 5. Docker-based Development
|
|
||||||
|
|
||||||
Stirling-PDF offers several Docker versions:
|
|
||||||
- Full: All features included
|
|
||||||
- Ultra-Lite: Basic PDF operations only
|
|
||||||
- Fat: Includes additional libraries and fonts predownloaded
|
|
||||||
|
|
||||||
### Example Docker Compose Files
|
|
||||||
|
|
||||||
Stirling-PDF provides several example Docker Compose files in the `exampleYmlFiles` directory such as :
|
|
||||||
|
|
||||||
- `docker-compose-latest.yml`: Latest version without security features
|
|
||||||
- `docker-compose-latest-security.yml`: Latest version with security features enabled
|
|
||||||
- `docker-compose-latest-fat-security.yml`: Fat version with security features enabled
|
|
||||||
|
|
||||||
These files provide pre-configured setups for different scenarios. For example, here's a snippet from `docker-compose-latest-security.yml`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
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"
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
To use these example files, copy the desired file to your project root and rename it to `docker-compose.yml`, or specify the file explicitly when running Docker Compose:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose -f exampleYmlFiles/docker-compose-latest-security.yml up
|
|
||||||
```
|
|
||||||
|
|
||||||
### Building Docker Images
|
|
||||||
|
|
||||||
Stirling-PDF uses different Docker images for various configurations. The build process is controlled by environment variables and uses specific Dockerfile variants. Here's how to build the Docker images:
|
|
||||||
|
|
||||||
1. Set the security environment variable:
|
|
||||||
```bash
|
|
||||||
export DOCKER_ENABLE_SECURITY=false # or true for security-enabled builds
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Build the project with Gradle:
|
|
||||||
```bash
|
|
||||||
./gradlew clean build
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Build the Docker images:
|
|
||||||
|
|
||||||
For the latest version:
|
|
||||||
```bash
|
|
||||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest -f ./Dockerfile .
|
|
||||||
```
|
|
||||||
|
|
||||||
For the ultra-lite version:
|
|
||||||
```bash
|
|
||||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-ultra-lite -f ./Dockerfile-ultra-lite .
|
|
||||||
```
|
|
||||||
|
|
||||||
For the fat version (with security enabled):
|
|
||||||
```bash
|
|
||||||
export DOCKER_ENABLE_SECURITY=true
|
|
||||||
docker build --no-cache --pull --build-arg VERSION_TAG=alpha -t frooodle/s-pdf:latest-fat -f ./Dockerfile-fat .
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: The `--no-cache` and `--pull` flags ensure that the build process uses the latest base images and doesn't use cached layers, which is useful for testing and ensuring reproducible builds. however to improve build times these can often be removed depending on your usecase
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## 6. Testing
|
|
||||||
|
|
||||||
### Comprehensive Testing Script
|
|
||||||
|
|
||||||
Stirling-PDF provides a `test.sh` script in the root directory. This script builds all versions of Stirling-PDF, checks that each version works, and runs Cucumber tests. It's recommended to run this script before submitting a final pull request.
|
|
||||||
|
|
||||||
To run the test script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
./test.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This script performs the following actions:
|
|
||||||
1. Builds all Docker images (full, ultra-lite, fat)
|
|
||||||
2. Runs each version to ensure it starts correctly
|
|
||||||
3. Executes Cucumber tests against main version and ensures feature compatibility, in the event these tests fail your PR will not be merged
|
|
||||||
|
|
||||||
Note: The `test.sh` script will run automatically when you raise a PR. However, it's recommended to run it locally first to save resources and catch any issues early.
|
|
||||||
|
|
||||||
### Full Testing with Docker
|
|
||||||
|
|
||||||
1. Build and run the Docker container per the above instructions:
|
|
||||||
|
|
||||||
2. Access the application at `http://localhost:8080` and manually test all features developed.
|
|
||||||
|
|
||||||
|
|
||||||
### Local Testing (Java and UI Components)
|
|
||||||
|
|
||||||
For quick iterations and development of Java backend, JavaScript, and UI components, you can run and test Stirling-PDF locally without Docker. This approach allows you to work on and verify changes to:
|
|
||||||
|
|
||||||
- Java backend logic
|
|
||||||
- RESTful API endpoints
|
|
||||||
- JavaScript functionality
|
|
||||||
- User interface components and styling
|
|
||||||
- Thymeleaf templates
|
|
||||||
|
|
||||||
To run Stirling-PDF locally:
|
|
||||||
|
|
||||||
1. Compile and run the project using built in IDE methods or by running:
|
|
||||||
```
|
|
||||||
./gradlew bootRun
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Access the application at `http://localhost:8080` in your web browser.
|
|
||||||
|
|
||||||
3. Manually test the features you're working on through the UI.
|
|
||||||
|
|
||||||
4. For API changes, use tools like Postman or curl to test endpoints directly.
|
|
||||||
|
|
||||||
Important notes:
|
|
||||||
- Local testing doesn't include features that depend on external tools like OCRmyPDF, LibreOffice, or Python scripts.
|
|
||||||
- There are currently no automated unit tests. All testing is done manually through the UI or API calls. (You are welcome to add JUnits!)
|
|
||||||
- Always verify your changes in the full Docker environment before submitting pull requests, as some integrations and features will only work in the complete setup.
|
|
||||||
|
|
||||||
|
|
||||||
## 7. Contributing
|
|
||||||
|
|
||||||
1. Fork the repository on GitHub.
|
|
||||||
2. Create a new branch for your feature or bug fix.
|
|
||||||
3. Make your changes and commit them with clear, descriptive messages and ensure any documentation is updated related to your changes.
|
|
||||||
4. Test your changes thoroughly in the Docker environment.
|
|
||||||
5. Run the `test.sh` script to ensure all versions build correctly and pass the Cucumber tests:
|
|
||||||
```bash
|
|
||||||
./test.sh
|
|
||||||
```
|
|
||||||
6. Push your changes to your fork.
|
|
||||||
7. Submit a pull request to the main repository.
|
|
||||||
8. See additional [contributing guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
|
|
||||||
|
|
||||||
When you raise a PR:
|
|
||||||
- The `test.sh` script will run automatically against your PR.
|
|
||||||
- The PR checks will verify versioning and dependency updates.
|
|
||||||
- Documentation will be automatically updated for dependency changes.
|
|
||||||
- Security issues will be checked using Snyk and PixeeBot.
|
|
||||||
|
|
||||||
Address any issues that arise from these checks before finalizing your pull request.
|
|
||||||
|
|
||||||
## 8. API Documentation
|
|
||||||
|
|
||||||
API documentation is available at `/swagger-ui/index.html` when running the application. You can also view the latest API documentation [here](https://app.swaggerhub.com/apis-docs/Stirling-Tools/Stirling-PDF/).
|
|
||||||
|
|
||||||
## 9. Customization
|
|
||||||
|
|
||||||
Stirling-PDF can be customized through environment variables or a `settings.yml` file. Key customization options include:
|
|
||||||
- Application name and branding
|
|
||||||
- Security settings
|
|
||||||
- UI customization
|
|
||||||
- Endpoint management
|
|
||||||
|
|
||||||
When using Docker, pass environment variables using the `-e` flag or in your `docker-compose.yml` file.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```
|
|
||||||
docker run -p 8080:8080 -e APP_NAME="My PDF Tool" stirling-pdf:full
|
|
||||||
```
|
|
||||||
|
|
||||||
Refer to the main README for a full list of customization options.
|
|
||||||
|
|
||||||
## 10. Language Translations
|
|
||||||
|
|
||||||
For managing language translations that affect multiple files, Stirling-PDF provides a helper script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
/scripts/replace_translation_line.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
This script helps you make consistent replacements across language files.
|
|
||||||
|
|
||||||
When contributing translations:
|
|
||||||
1. Use the helper script for multi-file changes.
|
|
||||||
2. Ensure all language files are updated consistently.
|
|
||||||
3. The PR checks will verify consistency in language file updates.
|
|
||||||
|
|
||||||
Remember to test your changes thoroughly to ensure they don't break any existing functionality.
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Code examples
|
|
||||||
|
|
||||||
### Overview of Thymeleaf
|
|
||||||
|
|
||||||
Thymeleaf is a server-side Java HTML template engine. It is used in Stirling-PDF to render dynamic web pages. Thymeleaf integrates heavily with Spring Boot
|
|
||||||
|
|
||||||
### Thymeleaf overview
|
|
||||||
|
|
||||||
In Stirling-PDF, Thymeleaf is used to create HTML templates that are rendered on the server side. These templates are located in the `src/main/resources/templates` directory. Thymeleaf templates use a combination of HTML and special Thymeleaf attributes to dynamically generate content.
|
|
||||||
Some examples of this are
|
|
||||||
```html
|
|
||||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
|
||||||
or
|
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
|
||||||
```
|
|
||||||
Where it uses the th:block, th: indicating its a special thymeleaf element to be used serverside in generating the html, and block being the actual element type.
|
|
||||||
In this case we are inserting the ``navbar`` entry within the ``fragments/navbar.html`` fragment into the ``th:block`` element.
|
|
||||||
|
|
||||||
They can be more complex such as
|
|
||||||
```html
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{pageExtracter.title}, header=#{pageExtracter.header})}"></th:block>
|
|
||||||
```
|
|
||||||
Which is the same as above but passes the parameters title and header into the fragment common.html to be used in its HTML generation
|
|
||||||
|
|
||||||
Thymeleaf can also be used to loop through objects or pass things from java side into html side.
|
|
||||||
```java
|
|
||||||
@GetMapping
|
|
||||||
public String newFeaturePage(Model model) {
|
|
||||||
model.addAttribute("exampleData", exampleData);
|
|
||||||
return "new-feature";
|
|
||||||
}
|
|
||||||
```
|
|
||||||
in above example if exampleData is a list of plain java objects of class Person and within it you had id, name, age etc. You can reference it like so
|
|
||||||
```html
|
|
||||||
<tbody>
|
|
||||||
<!-- Use th:each to iterate over the list -->
|
|
||||||
<tr th:each="person : ${exampleData}">
|
|
||||||
<td th:text="${person.id}"></td>
|
|
||||||
<td th:text="${person.name}"></td>
|
|
||||||
<td th:text="${person.age}"></td>
|
|
||||||
<td th:text="${person.email}"></td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
```
|
|
||||||
This would generate n entries of tr for each person in exampleData
|
|
||||||
|
|
||||||
### Adding a New Feature to the Backend (API)
|
|
||||||
|
|
||||||
1. **Create a New Controller:**
|
|
||||||
- Create a new Java class in the `src/main/java/stirling/software/SPDF/controller/api` directory.
|
|
||||||
- Annotate the class with `@RestController` and `@RequestMapping` to define the API endpoint.
|
|
||||||
- Ensure to add API documentation annotations like `@Tag(name = "General", description = "General APIs")` and `@Operation(summary = "Crops a PDF document", description = "This operation takes an input PDF file and crops it according to the given coordinates. Input:PDF Output:PDF Type:SISO")`.
|
|
||||||
|
|
||||||
```java
|
|
||||||
package stirling.software.SPDF.controller.api;
|
|
||||||
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/new-feature")
|
|
||||||
@Tag(name = "General", description = "General APIs")
|
|
||||||
public class NewFeatureController {
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
@Operation(summary = "New Feature", description = "This is a new feature endpoint.")
|
|
||||||
public String newFeature() {
|
|
||||||
return "NewFeatureResponse"; // This refers to the NewFeatureResponse.html template presenting the user with the generated html from that file when they navigate to /api/v1/new-feature
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Define the Service Layer:** (Not required but often useful)
|
|
||||||
- Create a new service class in the `src/main/java/stirling/software/SPDF/service` directory.
|
|
||||||
- Implement the business logic for the new feature.
|
|
||||||
|
|
||||||
```java
|
|
||||||
package stirling.software.SPDF.service;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
|
|
||||||
@Service
|
|
||||||
public class NewFeatureService {
|
|
||||||
|
|
||||||
public String getNewFeatureData() {
|
|
||||||
// Implement business logic here
|
|
||||||
return "New Feature Data";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2b. **Integrate the Service with the Controller:**
|
|
||||||
- Autowire the service class in the controller and use it to handle the API request.
|
|
||||||
|
|
||||||
```java
|
|
||||||
package stirling.software.SPDF.controller.api;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
import stirling.software.SPDF.service.NewFeatureService;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/api/v1/new-feature")
|
|
||||||
@Tag(name = "General", description = "General APIs")
|
|
||||||
public class NewFeatureController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private NewFeatureService newFeatureService;
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
@Operation(summary = "New Feature", description = "This is a new feature endpoint.")
|
|
||||||
public String newFeature() {
|
|
||||||
return newFeatureService.getNewFeatureData();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Adding a New Feature to the Frontend (UI)
|
|
||||||
|
|
||||||
1. **Create a New Thymeleaf Template:**
|
|
||||||
- Create a new HTML file in the `src/main/resources/templates` directory.
|
|
||||||
- Use Thymeleaf attributes to dynamically generate content.
|
|
||||||
- Use `extract-page.html` as a base example for the HTML template, useful to ensure importing of the general layout, navbar and footer.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
|
||||||
<head>
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{newFeature.title}, header=#{newFeature.header})}"></th:block>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<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 bg-card">
|
|
||||||
<div class="tool-header">
|
|
||||||
<span class="material-symbols-rounded tool-header-icon organize">upload</span>
|
|
||||||
<span class="tool-header-text" th:text="#{newFeature.header}"></span>
|
|
||||||
</div>
|
|
||||||
<form th:action="@{'/api/v1/new-feature'}" method="post" enctype="multipart/form-data">
|
|
||||||
<div th:replace="~{fragments/common :: fileSelector(name='fileInput', multipleInputsForSingleRequest=false, accept='application/pdf')}"></div>
|
|
||||||
<input type="hidden" id="customMode" name="customMode" value="">
|
|
||||||
<div class="mb-3">
|
|
||||||
<label for="featureInput" th:text="#{newFeature.prompt}"></label>
|
|
||||||
<input type="text" class="form-control" id="featureInput" name="featureInput" th:placeholder="#{newFeature.placeholder}" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" id="submitBtn" class="btn btn-primary" th:text="#{newFeature.submit}"></button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Create a New Controller for the UI:**
|
|
||||||
- Create a new Java class in the `src/main/java/stirling/software/SPDF/controller/ui` directory.
|
|
||||||
- Annotate the class with `@Controller` and `@RequestMapping` to define the UI endpoint.
|
|
||||||
|
|
||||||
```java
|
|
||||||
package stirling.software.SPDF.controller.ui;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.ui.Model;
|
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
|
||||||
import stirling.software.SPDF.service.NewFeatureService;
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
@RequestMapping("/new-feature")
|
|
||||||
public class NewFeatureUIController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private NewFeatureService newFeatureService;
|
|
||||||
|
|
||||||
@GetMapping
|
|
||||||
public String newFeaturePage(Model model) {
|
|
||||||
model.addAttribute("newFeatureData", newFeatureService.getNewFeatureData());
|
|
||||||
return "new-feature";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Update the Navigation Bar:**
|
|
||||||
- Add a link to the new feature page in the navigation bar.
|
|
||||||
- Update the `src/main/resources/templates/fragments/navbar.html` file.
|
|
||||||
|
|
||||||
```html
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" th:href="@{/new-feature}">New Feature</a>
|
|
||||||
</li>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Adding New Translations to Existing Language Files in Stirling-PDF
|
|
||||||
|
|
||||||
When adding a new feature or modifying existing ones in Stirling-PDF, you'll need to add new translation entries to the existing language files. Here's a step-by-step guide:
|
|
||||||
|
|
||||||
### 1. Locate Existing Language Files
|
|
||||||
|
|
||||||
Find the existing `messages.properties` files in the `src/main/resources` directory. You'll see files like:
|
|
||||||
|
|
||||||
- `messages.properties` (default, usually English)
|
|
||||||
- `messages_en_GB.properties`
|
|
||||||
- `messages_fr.properties`
|
|
||||||
- `messages_de.properties`
|
|
||||||
- etc.
|
|
||||||
|
|
||||||
### 2. Add New Translation Entries
|
|
||||||
|
|
||||||
Open each of these files and add your new translation entries. For example, if you're adding a new feature called "PDF Splitter",
|
|
||||||
Use descriptive, hierarchical keys (e.g., `feature.element.description`)
|
|
||||||
you might add:
|
|
||||||
|
|
||||||
```properties
|
|
||||||
pdfSplitter.title=PDF Splitter
|
|
||||||
pdfSplitter.description=Split your PDF into multiple documents
|
|
||||||
pdfSplitter.button.split=Split PDF
|
|
||||||
pdfSplitter.input.pages=Enter page numbers to split
|
|
||||||
```
|
|
||||||
|
|
||||||
Add these entries to the default GB language file and any others you wish, translating the values as appropriate for each language.
|
|
||||||
|
|
||||||
### 3. Use Translations in Thymeleaf Templates
|
|
||||||
|
|
||||||
In your Thymeleaf templates, use the `#{key}` syntax to reference the new translations:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<h1 th:text="#{pdfSplitter.title}">PDF Splitter</h1>
|
|
||||||
<p th:text="#{pdfSplitter.description}">Split your PDF into multiple documents</p>
|
|
||||||
<input type="text" th:placeholder="#{pdfSplitter.input.pages}">
|
|
||||||
<button th:text="#{pdfSplitter.button.split}">Split PDF</button>
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Remember, never hard-code text in your templates or Java code. Always use translation keys to ensure proper localization.
|
|
||||||
64
README.md
64
README.md
@@ -172,42 +172,42 @@ Stirling PDF currently supports 38!
|
|||||||
|
|
||||||
| Language | Progress |
|
| Language | Progress |
|
||||||
| ------------------------------------------- | -------------------------------------- |
|
| ------------------------------------------- | -------------------------------------- |
|
||||||
| Arabic (العربية) (ar_AR) |  |
|
| Arabic (العربية) (ar_AR) |  |
|
||||||
| Basque (Euskara) (eu_ES) |  |
|
| Basque (Euskara) (eu_ES) |  |
|
||||||
| Bulgarian (Български) (bg_BG) |  |
|
| Bulgarian (Български) (bg_BG) |  |
|
||||||
| Catalan (Català) (ca_CA) |  |
|
| Catalan (Català) (ca_CA) |  |
|
||||||
| Croatian (Hrvatski) (hr_HR) |  |
|
| Croatian (Hrvatski) (hr_HR) |  |
|
||||||
| Czech (Česky) (cs_CZ) |  |
|
| Czech (Česky) (cs_CZ) |  |
|
||||||
| Danish (Dansk) (da_DK) |  |
|
| Danish (Dansk) (da_DK) |  |
|
||||||
| Dutch (Nederlands) (nl_NL) |  |
|
| Dutch (Nederlands) (nl_NL) |  |
|
||||||
| English (English) (en_GB) |  |
|
| English (English) (en_GB) |  |
|
||||||
| English (US) (en_US) |  |
|
| English (US) (en_US) |  |
|
||||||
| French (Français) (fr_FR) |  |
|
| French (Français) (fr_FR) |  |
|
||||||
| German (Deutsch) (de_DE) |  |
|
| German (Deutsch) (de_DE) |  |
|
||||||
| Greek (Ελληνικά) (el_GR) |  |
|
| Greek (Ελληνικά) (el_GR) |  |
|
||||||
| Hindi (हिंदी) (hi_IN) |  |
|
| Hindi (हिंदी) (hi_IN) |  |
|
||||||
| Hungarian (Magyar) (hu_HU) |  |
|
| Hungarian (Magyar) (hu_HU) |  |
|
||||||
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
| Indonesia (Bahasa Indonesia) (id_ID) |  |
|
||||||
| Irish (Gaeilge) (ga_IE) |  |
|
| Irish (Gaeilge) (ga_IE) |  |
|
||||||
| Italian (Italiano) (it_IT) |  |
|
| Italian (Italiano) (it_IT) |  |
|
||||||
| Japanese (日本語) (ja_JP) |  |
|
| Japanese (日本語) (ja_JP) |  |
|
||||||
| Korean (한국어) (ko_KR) |  |
|
| Korean (한국어) (ko_KR) |  |
|
||||||
| Norwegian (Norsk) (no_NB) |  |
|
| Norwegian (Norsk) (no_NB) |  |
|
||||||
| Polish (Polski) (pl_PL) |  |
|
| Polish (Polski) (pl_PL) |  |
|
||||||
| Portuguese (Português) (pt_PT) |  |
|
| Portuguese (Português) (pt_PT) |  |
|
||||||
| Portuguese Brazilian (Português) (pt_BR) |  |
|
| Portuguese Brazilian (Português) (pt_BR) |  |
|
||||||
| Romanian (Română) (ro_RO) |  |
|
| Romanian (Română) (ro_RO) |  |
|
||||||
| Russian (Русский) (ru_RU) |  |
|
| Russian (Русский) (ru_RU) |  |
|
||||||
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
| Serbian Latin alphabet (Srpski) (sr_LATN_RS) |  |
|
||||||
| Simplified Chinese (简体中文) (zh_CN) |  |
|
| Simplified Chinese (简体中文) (zh_CN) |  |
|
||||||
| Slovakian (Slovensky) (sk_SK) |  |
|
| Slovakian (Slovensky) (sk_SK) |  |
|
||||||
| Spanish (Español) (es_ES) |  |
|
| Spanish (Español) (es_ES) |  |
|
||||||
| Swedish (Svenska) (sv_SE) |  |
|
| Swedish (Svenska) (sv_SE) |  |
|
||||||
| Thai (ไทย) (th_TH) |  |
|
| Thai (ไทย) (th_TH) |  |
|
||||||
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
| Traditional Chinese (繁體中文) (zh_TW) |  |
|
||||||
| Turkish (Türkçe) (tr_TR) |  |
|
| Turkish (Türkçe) (tr_TR) |  |
|
||||||
| Ukrainian (Українська) (uk_UA) |  |
|
| Ukrainian (Українська) (uk_UA) |  |
|
||||||
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
| Vietnamese (Tiếng Việt) (vi_VN) |  |
|
||||||
|
|
||||||
## Contributing (creating issues, translations, fixing bugs, etc.)
|
## Contributing (creating issues, translations, fixing bugs, etc.)
|
||||||
|
|
||||||
|
|||||||
30
build.gradle
30
build.gradle
@@ -16,13 +16,13 @@ ext {
|
|||||||
springBootVersion = "3.3.4"
|
springBootVersion = "3.3.4"
|
||||||
pdfboxVersion = "3.0.3"
|
pdfboxVersion = "3.0.3"
|
||||||
logbackVersion = "1.5.7"
|
logbackVersion = "1.5.7"
|
||||||
imageioVersion = "3.12.0"
|
imageioVersion = "3.11.0"
|
||||||
lombokVersion = "1.18.34"
|
lombokVersion = "1.18.34"
|
||||||
bouncycastleVersion = "1.78.1"
|
bouncycastleVersion = "1.78.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "stirling.software"
|
group = "stirling.software"
|
||||||
version = "0.30.1"
|
version = "0.30.0"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
// 17 is lowest but we support and recommend 21
|
// 17 is lowest but we support and recommend 21
|
||||||
@@ -32,9 +32,11 @@ java {
|
|||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url "https://build.shibboleth.net/nexus/content/repositories/releases/" }
|
maven {
|
||||||
|
url "https://build.shibboleth.net/nexus/content/repositories/releases/"
|
||||||
|
}
|
||||||
maven {
|
maven {
|
||||||
url 'https://build.shibboleth.net/maven/releases'
|
url "https://build.shibboleth.net/maven/releases/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -119,7 +121,7 @@ configurations.all {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
//security updates
|
//security updates
|
||||||
implementation "org.springframework:spring-webmvc:6.1.14"
|
implementation "org.springframework:spring-webmvc:6.1.13"
|
||||||
|
|
||||||
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
implementation("io.github.pixee:java-security-toolkit:1.2.0")
|
||||||
|
|
||||||
@@ -133,7 +135,7 @@ dependencies {
|
|||||||
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion"
|
||||||
implementation 'com.posthog.java:posthog:1.1.1'
|
implementation 'com.posthog.java:posthog:1.1.1'
|
||||||
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
implementation 'com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer:20240325.1'
|
||||||
|
|
||||||
|
|
||||||
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
if (System.getenv("DOCKER_ENABLE_SECURITY") != "false") {
|
||||||
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-security:$springBootVersion"
|
||||||
@@ -146,20 +148,12 @@ dependencies {
|
|||||||
//2.2.x requires rebuild of DB file.. need migration path
|
//2.2.x requires rebuild of DB file.. need migration path
|
||||||
runtimeOnly "com.h2database:h2:2.1.214"
|
runtimeOnly "com.h2database:h2:2.1.214"
|
||||||
// implementation "com.h2database:h2:2.2.224"
|
// implementation "com.h2database:h2:2.2.224"
|
||||||
constraints {
|
|
||||||
implementation "org.opensaml:opensaml-core"
|
|
||||||
implementation "org.opensaml:opensaml-saml-api"
|
|
||||||
implementation "org.opensaml:opensaml-saml-impl"
|
|
||||||
}
|
|
||||||
implementation "org.springframework.security:spring-security-saml2-service-provider"
|
|
||||||
|
|
||||||
implementation 'com.coveo:saml-client:5.0.0'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
|
||||||
|
|
||||||
// Batik
|
// Batik
|
||||||
implementation "org.apache.xmlgraphics:batik-all:1.18"
|
implementation "org.apache.xmlgraphics:batik-all:1.17"
|
||||||
|
|
||||||
// TwelveMonkeys
|
// TwelveMonkeys
|
||||||
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
|
runtimeOnly "com.twelvemonkeys.imageio:imageio-batik:$imageioVersion"
|
||||||
@@ -201,11 +195,11 @@ dependencies {
|
|||||||
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcprov-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
implementation "org.bouncycastle:bcpkix-jdk18on:$bouncycastleVersion"
|
||||||
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
implementation "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion"
|
||||||
implementation "io.micrometer:micrometer-core:1.13.6"
|
implementation "io.micrometer:micrometer-core:1.13.4"
|
||||||
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
implementation group: "com.google.zxing", name: "core", version: "3.5.3"
|
||||||
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
// https://mvnrepository.com/artifact/org.commonmark/commonmark
|
||||||
implementation "org.commonmark:commonmark:0.24.0"
|
implementation "org.commonmark:commonmark:0.23.0"
|
||||||
implementation "org.commonmark:commonmark-ext-gfm-tables:0.24.0"
|
implementation "org.commonmark:commonmark-ext-gfm-tables:0.23.0"
|
||||||
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
// https://mvnrepository.com/artifact/com.bucket4j/bucket4j_jdk17
|
||||||
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
implementation "com.bucket4j:bucket4j_jdk17-core:8.14.0"
|
||||||
implementation "com.fathzer:javaluator:3.0.5"
|
implementation "com.fathzer:javaluator:3.0.5"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
apiVersion: v2
|
apiVersion: v2
|
||||||
appVersion: 0.30.1
|
appVersion: 0.29.0
|
||||||
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
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Check if a key was provided
|
|
||||||
if [ $# -eq 0 ]; then
|
|
||||||
echo "Please provide a key to remove."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
key_to_remove="$1"
|
|
||||||
|
|
||||||
for file in ../src/main/resources/messages_*.properties; do
|
|
||||||
# If the key ends with a dot, remove all keys starting with it
|
|
||||||
if [[ "$key_to_remove" == *. ]]; then
|
|
||||||
sed -i "/^${key_to_remove//./\\.}/d" "$file"
|
|
||||||
else
|
|
||||||
# Otherwise, remove only the exact key match
|
|
||||||
sed -i "/^${key_to_remove//./\\.}=/d" "$file"
|
|
||||||
fi
|
|
||||||
echo "Updated $file"
|
|
||||||
done
|
|
||||||
@@ -7,19 +7,20 @@ 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;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@Lazy
|
@Lazy
|
||||||
@Slf4j
|
|
||||||
public class EEAppConfig {
|
public class EEAppConfig {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(EEAppConfig.class);
|
||||||
|
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
@Autowired private LicenseKeyChecker licenseKeyChecker;
|
||||||
|
|
||||||
@Bean(name = "runningEE")
|
@Bean(name = "runningEE")
|
||||||
public boolean runningEnterpriseEdition() {
|
public boolean runningEnterpriseEdition() {
|
||||||
return licenseKeyChecker.getEnterpriseEnabledResult();
|
return licenseKeyChecker.getEnterpriseEnabledResult();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,15 +33,11 @@ public class SPdfApplication {
|
|||||||
@Autowired private Environment env;
|
@Autowired private Environment env;
|
||||||
@Autowired ApplicationProperties applicationProperties;
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
private static String baseUrlStatic;
|
|
||||||
private static String serverPortStatic;
|
private static String serverPortStatic;
|
||||||
|
|
||||||
@Value("${baseUrl:http://localhost}")
|
|
||||||
private String baseUrl;
|
|
||||||
|
|
||||||
@Value("${server.port:8080}")
|
@Value("${server.port:8080}")
|
||||||
public void setServerPortStatic(String port) {
|
public void setServerPortStatic(String port) {
|
||||||
if ("auto".equalsIgnoreCase(port)) {
|
if (port.equalsIgnoreCase("auto")) {
|
||||||
// Use Spring Boot's automatic port assignment (server.port=0)
|
// Use Spring Boot's automatic port assignment (server.port=0)
|
||||||
SPdfApplication.serverPortStatic =
|
SPdfApplication.serverPortStatic =
|
||||||
"0"; // This will let Spring Boot assign an available port
|
"0"; // This will let Spring Boot assign an available port
|
||||||
@@ -69,13 +65,12 @@ public class SPdfApplication {
|
|||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
baseUrlStatic = this.baseUrl;
|
|
||||||
// Check if the BROWSER_OPEN environment variable is set to true
|
// Check if the BROWSER_OPEN environment variable is set to true
|
||||||
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
String browserOpenEnv = env.getProperty("BROWSER_OPEN");
|
||||||
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
boolean browserOpen = browserOpenEnv != null && "true".equalsIgnoreCase(browserOpenEnv);
|
||||||
if (browserOpen) {
|
if (browserOpen) {
|
||||||
try {
|
try {
|
||||||
String url = baseUrl + ":" + getStaticPort();
|
String url = "http://localhost:" + getStaticPort();
|
||||||
|
|
||||||
String os = System.getProperty("os.name").toLowerCase();
|
String os = System.getProperty("os.name").toLowerCase();
|
||||||
Runtime rt = Runtime.getRuntime();
|
Runtime rt = Runtime.getRuntime();
|
||||||
@@ -83,9 +78,9 @@ public class SPdfApplication {
|
|||||||
// For Windows
|
// For Windows
|
||||||
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
SystemCommand.runCommand(rt, "rundll32 url.dll,FileProtocolHandler " + url);
|
||||||
} else if (os.contains("mac")) {
|
} else if (os.contains("mac")) {
|
||||||
SystemCommand.runCommand(rt, "open " + url);
|
rt.exec("open " + url);
|
||||||
} else if (os.contains("nix") || os.contains("nux")) {
|
} else if (os.contains("nix") || os.contains("nux")) {
|
||||||
SystemCommand.runCommand(rt, "xdg-open " + url);
|
rt.exec("xdg-open " + url);
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error("Error opening browser: {}", e.getMessage());
|
logger.error("Error opening browser: {}", e.getMessage());
|
||||||
@@ -143,18 +138,10 @@ public class SPdfApplication {
|
|||||||
|
|
||||||
private static void printStartupLogs() {
|
private static void printStartupLogs() {
|
||||||
logger.info("Stirling-PDF Started.");
|
logger.info("Stirling-PDF Started.");
|
||||||
String url = baseUrlStatic + ":" + getStaticPort();
|
String url = "http://localhost:" + getStaticPort();
|
||||||
logger.info("Navigate to {}", url);
|
logger.info("Navigate to {}", url);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getStaticBaseUrl() {
|
|
||||||
return baseUrlStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNonStaticBaseUrl() {
|
|
||||||
return baseUrlStatic;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getStaticPort() {
|
public static String getStaticPort() {
|
||||||
return serverPortStatic;
|
return serverPortStatic;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ 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;
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
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.springframework.core.io.ResourceLoader;
|
||||||
@@ -163,14 +162,12 @@ public class AppConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "analyticsPrompt")
|
@Bean(name = "analyticsPrompt")
|
||||||
@Scope("request")
|
|
||||||
public boolean analyticsPrompt() {
|
public boolean analyticsPrompt() {
|
||||||
return applicationProperties.getSystem().getEnableAnalytics() == null
|
return applicationProperties.getSystem().getEnableAnalytics() == null
|
||||||
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
|| "undefined".equals(applicationProperties.getSystem().getEnableAnalytics());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean(name = "analyticsEnabled")
|
@Bean(name = "analyticsEnabled")
|
||||||
@Scope("request")
|
|
||||||
public boolean analyticsEnabled() {
|
public boolean analyticsEnabled() {
|
||||||
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
if (applicationProperties.getEnterpriseEdition().isEnabled()) return true;
|
||||||
return applicationProperties.getSystem().getEnableAnalytics() != null
|
return applicationProperties.getSystem().getEnableAnalytics() != null
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import org.springframework.core.Ordered;
|
|||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import io.micrometer.common.util.StringUtils;
|
|
||||||
import jakarta.annotation.PostConstruct;
|
import jakarta.annotation.PostConstruct;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
@@ -40,26 +39,4 @@ public class InitialSetup {
|
|||||||
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
applicationProperties.getAutomaticallyGenerated().setKey(secretKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
|
||||||
public void initLegalUrls() throws IOException {
|
|
||||||
// Initialize Terms and Conditions
|
|
||||||
String termsUrl = applicationProperties.getLegal().getTermsAndConditions();
|
|
||||||
if (StringUtils.isEmpty(termsUrl)) {
|
|
||||||
String defaultTermsUrl = "https://www.stirlingpdf.com/terms-and-conditions";
|
|
||||||
GeneralUtils.saveKeyToConfig("legal.termsAndConditions", defaultTermsUrl);
|
|
||||||
applicationProperties.getLegal().setTermsAndConditions(defaultTermsUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize Privacy Policy
|
|
||||||
String privacyUrl = applicationProperties.getLegal().getPrivacyPolicy();
|
|
||||||
if (StringUtils.isEmpty(privacyUrl)) {
|
|
||||||
String defaultPrivacyUrl = "https://www.stirlingpdf.com/privacy-policy";
|
|
||||||
GeneralUtils.saveKeyToConfig("legal.privacyPolicy", defaultPrivacyUrl);
|
|
||||||
applicationProperties.getLegal().setPrivacyPolicy(defaultPrivacyUrl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,237 +1,27 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
|
||||||
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
import com.coveo.saml.SamlClient;
|
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.SPdfApplication;
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.Provider;
|
|
||||||
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
|
||||||
import stirling.software.SPDF.utils.UrlUtils;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
public class CustomLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
private final ApplicationProperties applicationProperties;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLogoutSuccess(
|
public void onLogoutSuccess(
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
throws IOException, ServletException {
|
throws IOException, ServletException {
|
||||||
|
|
||||||
if (!response.isCommitted()) {
|
if (request.getParameter("userIsDisabled") != null) {
|
||||||
// Handle user logout due to disabled account
|
getRedirectStrategy()
|
||||||
if (request.getParameter("userIsDisabled") != null) {
|
.sendRedirect(request, response, "/login?erroroauth=userIsDisabled");
|
||||||
response.sendRedirect(
|
return;
|
||||||
request.getContextPath() + "/login?erroroauth=userIsDisabled");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle OAuth2 authentication error
|
|
||||||
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
|
||||||
response.sendRedirect(
|
|
||||||
request.getContextPath() + "/login?erroroauth=userAlreadyExistsWeb");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (authentication != null) {
|
|
||||||
// Handle SAML2 logout redirection
|
|
||||||
if (authentication instanceof Saml2Authentication) {
|
|
||||||
getRedirect_saml2(request, response, authentication);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle OAuth2 logout redirection
|
|
||||||
else if (authentication instanceof OAuth2AuthenticationToken) {
|
|
||||||
getRedirect_oauth2(request, response, authentication);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle Username/Password logout
|
|
||||||
else if (authentication instanceof UsernamePasswordAuthenticationToken) {
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// Handle unknown authentication types
|
|
||||||
else {
|
|
||||||
log.error(
|
|
||||||
"authentication class unknown: "
|
|
||||||
+ authentication.getClass().getSimpleName());
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Redirect to login page after logout
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect for SAML2 authentication logout
|
|
||||||
private void getRedirect_saml2(
|
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
|
||||||
throws IOException {
|
|
||||||
|
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
|
||||||
String registrationId = samlConf.getRegistrationId();
|
|
||||||
|
|
||||||
Saml2Authentication samlAuthentication = (Saml2Authentication) authentication;
|
|
||||||
CustomSaml2AuthenticatedPrincipal principal =
|
|
||||||
(CustomSaml2AuthenticatedPrincipal) samlAuthentication.getPrincipal();
|
|
||||||
|
|
||||||
String nameIdValue = principal.getName();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Read certificate from the resource
|
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
|
||||||
X509Certificate certificate = CertificateUtils.readCertificate(certificateResource);
|
|
||||||
|
|
||||||
List<X509Certificate> certificates = new ArrayList<>();
|
|
||||||
certificates.add(certificate);
|
|
||||||
|
|
||||||
// Construct URLs required for SAML configuration
|
|
||||||
String serverUrl =
|
|
||||||
SPdfApplication.getStaticBaseUrl() + ":" + SPdfApplication.getStaticPort();
|
|
||||||
|
|
||||||
String relyingPartyIdentifier =
|
|
||||||
serverUrl + "/saml2/service-provider-metadata/" + registrationId;
|
|
||||||
|
|
||||||
String assertionConsumerServiceUrl = serverUrl + "/login/saml2/sso/" + registrationId;
|
|
||||||
|
|
||||||
String idpUrl = samlConf.getIdpSingleLogoutUrl();
|
|
||||||
|
|
||||||
String idpIssuer = samlConf.getIdpIssuer();
|
|
||||||
|
|
||||||
// Create SamlClient instance for SAML logout
|
|
||||||
SamlClient samlClient =
|
|
||||||
new SamlClient(
|
|
||||||
relyingPartyIdentifier,
|
|
||||||
assertionConsumerServiceUrl,
|
|
||||||
idpUrl,
|
|
||||||
idpIssuer,
|
|
||||||
certificates,
|
|
||||||
SamlClient.SamlIdpBinding.POST);
|
|
||||||
|
|
||||||
// Read private key for service provider
|
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
|
||||||
RSAPrivateKey privateKey = CertificateUtils.readPrivateKey(privateKeyResource);
|
|
||||||
|
|
||||||
// Set service provider keys for the SamlClient
|
|
||||||
samlClient.setSPKeys(certificate, privateKey);
|
|
||||||
|
|
||||||
// Redirect to identity provider for logout
|
|
||||||
samlClient.redirectToIdentityProvider(response, null, nameIdValue);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(nameIdValue, e);
|
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Redirect for OAuth2 authentication logout
|
|
||||||
private void getRedirect_oauth2(
|
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
|
||||||
throws IOException {
|
|
||||||
String param = "logout=true";
|
|
||||||
String registrationId = null;
|
|
||||||
String issuer = null;
|
|
||||||
String clientId = null;
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
|
|
||||||
if (authentication instanceof OAuth2AuthenticationToken) {
|
|
||||||
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
|
||||||
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Get OAuth2 provider details from configuration
|
|
||||||
Provider provider = oauth.getClient().get(registrationId);
|
|
||||||
issuer = provider.getIssuer();
|
|
||||||
clientId = provider.getClientId();
|
|
||||||
} catch (UnsupportedProviderException e) {
|
|
||||||
log.error(e.getMessage());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
|
||||||
issuer = oauth.getIssuer();
|
|
||||||
clientId = oauth.getClientId();
|
|
||||||
}
|
|
||||||
String errorMessage = "";
|
|
||||||
// Handle different error scenarios during logout
|
|
||||||
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
|
||||||
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
|
||||||
} else if ((errorMessage = request.getParameter("error")) != null) {
|
|
||||||
param = "error=" + sanitizeInput(errorMessage);
|
|
||||||
} else if ((errorMessage = request.getParameter("erroroauth")) != null) {
|
|
||||||
param = "erroroauth=" + sanitizeInput(errorMessage);
|
|
||||||
} else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
|
|
||||||
param = "error=oauth2AutoCreateDisabled";
|
|
||||||
} else if (request.getParameter("oauth2_admin_blocked_user") != null) {
|
|
||||||
param = "erroroauth=oauth2_admin_blocked_user";
|
|
||||||
} else if (request.getParameter("userIsDisabled") != null) {
|
|
||||||
param = "erroroauth=userIsDisabled";
|
|
||||||
} else if (request.getParameter("badcredentials") != null) {
|
|
||||||
param = "error=badcredentials";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
getRedirectStrategy().sendRedirect(request, response, "/login?logout=true");
|
||||||
|
|
||||||
// Redirect based on OAuth2 provider
|
|
||||||
switch (registrationId.toLowerCase()) {
|
|
||||||
case "keycloak":
|
|
||||||
// Add Keycloak specific logout URL if needed
|
|
||||||
String logoutUrl =
|
|
||||||
issuer
|
|
||||||
+ "/protocol/openid-connect/logout"
|
|
||||||
+ "?client_id="
|
|
||||||
+ clientId
|
|
||||||
+ "&post_logout_redirect_uri="
|
|
||||||
+ response.encodeRedirectURL(redirect_url);
|
|
||||||
log.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
|
||||||
response.sendRedirect(logoutUrl);
|
|
||||||
break;
|
|
||||||
case "github":
|
|
||||||
// Add GitHub specific logout URL if needed
|
|
||||||
String githubLogoutUrl = "https://github.com/logout";
|
|
||||||
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
|
||||||
response.sendRedirect(githubLogoutUrl);
|
|
||||||
break;
|
|
||||||
case "google":
|
|
||||||
// Add Google specific logout URL if needed
|
|
||||||
// String googleLogoutUrl =
|
|
||||||
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
|
||||||
// + response.encodeRedirectURL(redirect_url);
|
|
||||||
log.info("Google does not have a specific logout URL");
|
|
||||||
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
|
||||||
// response.sendRedirect(googleLogoutUrl);
|
|
||||||
// break;
|
|
||||||
default:
|
|
||||||
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
|
|
||||||
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
|
|
||||||
response.sendRedirect(defaultRedirectUrl);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize input to avoid potential security vulnerabilities
|
|
||||||
private String sanitizeInput(String input) {
|
|
||||||
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,22 @@
|
|||||||
package stirling.software.SPDF.config.security;
|
package stirling.software.SPDF.config.security;
|
||||||
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
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.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;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
import org.springframework.security.authentication.AuthenticationProvider;
|
import org.springframework.security.authentication.AuthenticationProvider;
|
||||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
|
||||||
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.GrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
|
||||||
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.OAuth2UserAuthority;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential;
|
|
||||||
import org.springframework.security.saml2.core.Saml2X509Credential.Saml2X509CredentialType;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider;
|
||||||
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
|
||||||
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
import org.springframework.security.saml2.provider.service.web.authentication.Saml2WebSsoAuthenticationFilter;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
@@ -42,20 +28,13 @@ import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2AuthenticationSuccessHandler;
|
||||||
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2LogoutSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
import stirling.software.SPDF.config.security.oauth2.CustomOAuth2UserService;
|
||||||
import stirling.software.SPDF.config.security.saml2.CertificateUtils;
|
import stirling.software.SPDF.config.security.saml.ConvertResponseToAuthentication;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationFailureHandler;
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationFailureHandler;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticationSuccessHandler;
|
import stirling.software.SPDF.config.security.saml.CustomSAMLAuthenticationSuccessHandler;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2ResponseAuthenticationConverter;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
|
||||||
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
import stirling.software.SPDF.repository.JPATokenRepositoryImpl;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@@ -66,6 +45,12 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Autowired private CustomUserDetailsService userDetailsService;
|
@Autowired private CustomUserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private GrantedAuthoritiesMapper userAuthoritiesMapper;
|
||||||
|
|
||||||
|
@Autowired(required = false)
|
||||||
|
private RelyingPartyRegistrationRepository relyingPartyRegistrationRepository;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public PasswordEncoder passwordEncoder() {
|
public PasswordEncoder passwordEncoder() {
|
||||||
return new BCryptPasswordEncoder();
|
return new BCryptPasswordEncoder();
|
||||||
@@ -86,8 +71,11 @@ public class SecurityConfiguration {
|
|||||||
@Autowired private FirstLoginFilter firstLoginFilter;
|
@Autowired private FirstLoginFilter firstLoginFilter;
|
||||||
@Autowired private SessionPersistentRegistry sessionRegistry;
|
@Autowired private SessionPersistentRegistry sessionRegistry;
|
||||||
|
|
||||||
|
@Autowired private ConvertResponseToAuthentication convertResponseToAuthentication;
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
|
http.authenticationManager(authenticationManager(http));
|
||||||
|
|
||||||
if (loginEnabledValue) {
|
if (loginEnabledValue) {
|
||||||
http.addFilterBefore(
|
http.addFilterBefore(
|
||||||
@@ -106,116 +94,128 @@ public class SecurityConfiguration {
|
|||||||
.sessionRegistry(sessionRegistry)
|
.sessionRegistry(sessionRegistry)
|
||||||
.expiredUrl("/login?logout=true"));
|
.expiredUrl("/login?logout=true"));
|
||||||
|
|
||||||
http.authenticationProvider(daoAuthenticationProvider());
|
http.formLogin(
|
||||||
http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()));
|
formLogin ->
|
||||||
http.logout(
|
formLogin
|
||||||
logout ->
|
.loginPage("/login")
|
||||||
logout.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
|
.successHandler(
|
||||||
.logoutSuccessHandler(
|
new CustomAuthenticationSuccessHandler(
|
||||||
new CustomLogoutSuccessHandler(applicationProperties))
|
loginAttemptService, userService))
|
||||||
.invalidateHttpSession(true) // Invalidate session
|
.defaultSuccessUrl("/")
|
||||||
.deleteCookies("JSESSIONID", "remember-me"));
|
.failureHandler(
|
||||||
http.rememberMe(
|
new CustomAuthenticationFailureHandler(
|
||||||
rememberMeConfigurer ->
|
loginAttemptService, userService))
|
||||||
rememberMeConfigurer // Use the configurator directly
|
.permitAll())
|
||||||
.key("uniqueAndSecret")
|
.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache()))
|
||||||
.tokenRepository(persistentTokenRepository())
|
.logout(
|
||||||
.tokenValiditySeconds(1209600) // 2 weeks
|
logout ->
|
||||||
);
|
logout.logoutRequestMatcher(
|
||||||
http.authorizeHttpRequests(
|
new AntPathRequestMatcher("/logout"))
|
||||||
authz ->
|
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
|
||||||
authz.requestMatchers(
|
.invalidateHttpSession(true) // Invalidate session
|
||||||
req -> {
|
.deleteCookies("JSESSIONID", "remember-me"))
|
||||||
String uri = req.getRequestURI();
|
.rememberMe(
|
||||||
String contextPath = req.getContextPath();
|
rememberMeConfigurer ->
|
||||||
|
rememberMeConfigurer // Use the configurator directly
|
||||||
|
.key("uniqueAndSecret")
|
||||||
|
.tokenRepository(persistentTokenRepository())
|
||||||
|
.tokenValiditySeconds(1209600) // 2 weeks
|
||||||
|
)
|
||||||
|
.authorizeHttpRequests(
|
||||||
|
authz ->
|
||||||
|
authz.requestMatchers(
|
||||||
|
req -> {
|
||||||
|
String uri = req.getRequestURI();
|
||||||
|
String contextPath = req.getContextPath();
|
||||||
|
|
||||||
// Remove the context path from the URI
|
// Remove the context path from the URI
|
||||||
String trimmedUri =
|
String trimmedUri =
|
||||||
uri.startsWith(contextPath)
|
uri.startsWith(contextPath)
|
||||||
? uri.substring(
|
? uri.substring(
|
||||||
contextPath.length())
|
contextPath
|
||||||
: uri;
|
.length())
|
||||||
|
: uri;
|
||||||
|
|
||||||
return trimmedUri.startsWith("/login")
|
return trimmedUri.startsWith("/login")
|
||||||
|| trimmedUri.startsWith("/oauth")
|
|| trimmedUri.startsWith("/oauth")
|
||||||
|| trimmedUri.startsWith("/saml2")
|
|| trimmedUri.startsWith("/saml2")
|
||||||
|| trimmedUri.endsWith(".svg")
|
|| trimmedUri.endsWith(".svg")
|
||||||
|| trimmedUri.startsWith("/register")
|
|| trimmedUri.startsWith(
|
||||||
|| trimmedUri.startsWith("/error")
|
"/register")
|
||||||
|| trimmedUri.startsWith("/images/")
|
|| trimmedUri.startsWith("/error")
|
||||||
|| trimmedUri.startsWith("/public/")
|
|| trimmedUri.startsWith("/images/")
|
||||||
|| trimmedUri.startsWith("/css/")
|
|| trimmedUri.startsWith("/public/")
|
||||||
|| trimmedUri.startsWith("/fonts/")
|
|| trimmedUri.startsWith("/css/")
|
||||||
|| trimmedUri.startsWith("/js/")
|
|| trimmedUri.startsWith("/fonts/")
|
||||||
|| trimmedUri.startsWith(
|
|| trimmedUri.startsWith("/js/")
|
||||||
"/api/v1/info/status");
|
|| trimmedUri.startsWith(
|
||||||
})
|
"/api/v1/info/status");
|
||||||
.permitAll()
|
})
|
||||||
.anyRequest()
|
.permitAll()
|
||||||
.authenticated());
|
.anyRequest()
|
||||||
|
.authenticated());
|
||||||
// Handle User/Password Logins
|
|
||||||
if (applicationProperties.getSecurity().isUserPass()) {
|
|
||||||
http.formLogin(
|
|
||||||
formLogin ->
|
|
||||||
formLogin
|
|
||||||
.loginPage("/login")
|
|
||||||
.successHandler(
|
|
||||||
new CustomAuthenticationSuccessHandler(
|
|
||||||
loginAttemptService, userService))
|
|
||||||
.failureHandler(
|
|
||||||
new CustomAuthenticationFailureHandler(
|
|
||||||
loginAttemptService, userService))
|
|
||||||
.defaultSuccessUrl("/")
|
|
||||||
.permitAll());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle OAUTH2 Logins
|
// Handle OAUTH2 Logins
|
||||||
if (applicationProperties.getSecurity().isOauth2Activ()) {
|
if (applicationProperties.getSecurity().getOauth2() != null
|
||||||
|
&& applicationProperties.getSecurity().getOauth2().getEnabled()
|
||||||
|
&& !applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getLoginMethod()
|
||||||
|
.equalsIgnoreCase("normal")) {
|
||||||
|
|
||||||
http.oauth2Login(
|
http.oauth2Login(
|
||||||
oauth2 ->
|
oauth2 ->
|
||||||
oauth2.loginPage("/oauth2")
|
oauth2.loginPage("/oauth2")
|
||||||
/*
|
/*
|
||||||
This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database.
|
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'
|
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.
|
is set as true, else login fails with an error message advising the same.
|
||||||
*/
|
*/
|
||||||
.successHandler(
|
|
||||||
new CustomOAuth2AuthenticationSuccessHandler(
|
|
||||||
loginAttemptService,
|
|
||||||
applicationProperties,
|
|
||||||
userService))
|
|
||||||
.failureHandler(
|
|
||||||
new CustomOAuth2AuthenticationFailureHandler())
|
|
||||||
// Add existing Authorities from the database
|
|
||||||
.userInfoEndpoint(
|
|
||||||
userInfoEndpoint ->
|
|
||||||
userInfoEndpoint
|
|
||||||
.oidcUserService(
|
|
||||||
new CustomOAuth2UserService(
|
|
||||||
applicationProperties,
|
|
||||||
userService,
|
|
||||||
loginAttemptService))
|
|
||||||
.userAuthoritiesMapper(
|
|
||||||
userAuthoritiesMapper()))
|
|
||||||
.permitAll());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle SAML
|
|
||||||
if (applicationProperties.getSecurity().isSaml2Activ() && applicationProperties.getSystem().getEnableAlphaFunctionality()) {
|
|
||||||
http.authenticationProvider(samlAuthenticationProvider());
|
|
||||||
http.saml2Login(
|
|
||||||
saml2 ->
|
|
||||||
saml2.loginPage("/saml2")
|
|
||||||
.successHandler(
|
.successHandler(
|
||||||
new CustomSaml2AuthenticationSuccessHandler(
|
new CustomOAuth2AuthenticationSuccessHandler(
|
||||||
loginAttemptService,
|
loginAttemptService,
|
||||||
applicationProperties,
|
applicationProperties,
|
||||||
userService))
|
userService))
|
||||||
.failureHandler(
|
.failureHandler(
|
||||||
new CustomSaml2AuthenticationFailureHandler())
|
new CustomOAuth2AuthenticationFailureHandler())
|
||||||
.permitAll())
|
// Add existing Authorities from the database
|
||||||
|
.userInfoEndpoint(
|
||||||
|
userInfoEndpoint ->
|
||||||
|
userInfoEndpoint
|
||||||
|
.oidcUserService(
|
||||||
|
new CustomOAuth2UserService(
|
||||||
|
applicationProperties,
|
||||||
|
userService,
|
||||||
|
loginAttemptService))
|
||||||
|
.userAuthoritiesMapper(
|
||||||
|
userAuthoritiesMapper)))
|
||||||
|
.logout(
|
||||||
|
logout ->
|
||||||
|
logout.logoutSuccessHandler(
|
||||||
|
new CustomOAuth2LogoutSuccessHandler(
|
||||||
|
applicationProperties)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle SAML
|
||||||
|
if (applicationProperties.getSecurity().getSaml() != null
|
||||||
|
&& applicationProperties.getSecurity().getSaml().getEnabled()
|
||||||
|
&& !applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getLoginMethod()
|
||||||
|
.equalsIgnoreCase("normal")) {
|
||||||
|
http.saml2Login(
|
||||||
|
saml2 -> {
|
||||||
|
saml2.loginPage("/saml2")
|
||||||
|
.relyingPartyRegistrationRepository(
|
||||||
|
relyingPartyRegistrationRepository)
|
||||||
|
.successHandler(
|
||||||
|
new CustomSAMLAuthenticationSuccessHandler(
|
||||||
|
loginAttemptService,
|
||||||
|
userService,
|
||||||
|
applicationProperties))
|
||||||
|
.failureHandler(
|
||||||
|
new CustomSAMLAuthenticationFailureHandler());
|
||||||
|
})
|
||||||
.addFilterBefore(
|
.addFilterBefore(
|
||||||
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
userAuthenticationFilter, Saml2WebSsoAuthenticationFilter.class);
|
||||||
}
|
}
|
||||||
@@ -231,234 +231,39 @@ public class SecurityConfiguration {
|
|||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
@ConditionalOnProperty(
|
||||||
name = "security.saml2.enabled",
|
name = "security.saml.enabled",
|
||||||
havingValue = "true",
|
havingValue = "true",
|
||||||
matchIfMissing = false)
|
matchIfMissing = false)
|
||||||
public AuthenticationProvider samlAuthenticationProvider() {
|
public AuthenticationProvider samlAuthenticationProvider() {
|
||||||
OpenSaml4AuthenticationProvider authenticationProvider =
|
OpenSaml4AuthenticationProvider authenticationProvider =
|
||||||
new OpenSaml4AuthenticationProvider();
|
new OpenSaml4AuthenticationProvider();
|
||||||
authenticationProvider.setResponseAuthenticationConverter(
|
authenticationProvider.setResponseAuthenticationConverter(convertResponseToAuthentication);
|
||||||
new CustomSaml2ResponseAuthenticationConverter(userService));
|
|
||||||
return authenticationProvider;
|
return authenticationProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client Registration Repository for OAUTH2 OIDC Login
|
// @Bean
|
||||||
@Bean
|
// public AuthenticationProvider daoAuthenticationProvider() {
|
||||||
@ConditionalOnProperty(
|
// DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||||
value = "security.oauth2.enabled",
|
// provider.setUserDetailsService(userDetailsService); // UserDetailsService
|
||||||
havingValue = "true",
|
// provider.setPasswordEncoder(passwordEncoder()); // PasswordEncoder
|
||||||
matchIfMissing = false)
|
// return provider;
|
||||||
public ClientRegistrationRepository clientRegistrationRepository() {
|
// }
|
||||||
List<ClientRegistration> registrations = new ArrayList<>();
|
|
||||||
|
|
||||||
githubClientRegistration().ifPresent(registrations::add);
|
|
||||||
oidcClientRegistration().ifPresent(registrations::add);
|
|
||||||
googleClientRegistration().ifPresent(registrations::add);
|
|
||||||
keycloakClientRegistration().ifPresent(registrations::add);
|
|
||||||
|
|
||||||
if (registrations.isEmpty()) {
|
|
||||||
log.error("At least one OAuth2 provider must be configured");
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new InMemoryClientRegistrationRepository(registrations);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> googleClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
return google != null && google.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(google.getName())
|
|
||||||
.clientId(google.getClientId())
|
|
||||||
.clientSecret(google.getClientSecret())
|
|
||||||
.scope(google.getScopes())
|
|
||||||
.authorizationUri(google.getAuthorizationuri())
|
|
||||||
.tokenUri(google.getTokenuri())
|
|
||||||
.userInfoUri(google.getUserinfouri())
|
|
||||||
.userNameAttributeName(google.getUseAsUsername())
|
|
||||||
.clientName(google.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + google.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> keycloakClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
|
||||||
|
|
||||||
return keycloak != null && keycloak.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(keycloak.getIssuer())
|
|
||||||
.registrationId(keycloak.getName())
|
|
||||||
.clientId(keycloak.getClientId())
|
|
||||||
.clientSecret(keycloak.getClientSecret())
|
|
||||||
.scope(keycloak.getScopes())
|
|
||||||
.userNameAttributeName(keycloak.getUseAsUsername())
|
|
||||||
.clientName(keycloak.getClientName())
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> githubClientRegistration() {
|
|
||||||
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null || !oauth.getEnabled()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client == null) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
GithubProvider github = client.getGithub();
|
|
||||||
return github != null && github.isSettingsValid()
|
|
||||||
? Optional.of(
|
|
||||||
ClientRegistration.withRegistrationId(github.getName())
|
|
||||||
.clientId(github.getClientId())
|
|
||||||
.clientSecret(github.getClientSecret())
|
|
||||||
.scope(github.getScopes())
|
|
||||||
.authorizationUri(github.getAuthorizationuri())
|
|
||||||
.tokenUri(github.getTokenuri())
|
|
||||||
.userInfoUri(github.getUserinfouri())
|
|
||||||
.userNameAttributeName(github.getUseAsUsername())
|
|
||||||
.clientName(github.getClientName())
|
|
||||||
.redirectUri("{baseUrl}/login/oauth2/code/" + github.getName())
|
|
||||||
.authorizationGrantType(
|
|
||||||
org.springframework.security.oauth2.core
|
|
||||||
.AuthorizationGrantType.AUTHORIZATION_CODE)
|
|
||||||
.build())
|
|
||||||
: Optional.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Optional<ClientRegistration> oidcClientRegistration() {
|
|
||||||
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
|
||||||
if (oauth == null
|
|
||||||
|| oauth.getIssuer() == null
|
|
||||||
|| oauth.getIssuer().isEmpty()
|
|
||||||
|| oauth.getClientId() == null
|
|
||||||
|| oauth.getClientId().isEmpty()
|
|
||||||
|| oauth.getClientSecret() == null
|
|
||||||
|| oauth.getClientSecret().isEmpty()
|
|
||||||
|| oauth.getScopes() == null
|
|
||||||
|| oauth.getScopes().isEmpty()
|
|
||||||
|| oauth.getUseAsUsername() == null
|
|
||||||
|| oauth.getUseAsUsername().isEmpty()) {
|
|
||||||
return Optional.empty();
|
|
||||||
}
|
|
||||||
return Optional.of(
|
|
||||||
ClientRegistrations.fromIssuerLocation(oauth.getIssuer())
|
|
||||||
.registrationId("oidc")
|
|
||||||
.clientId(oauth.getClientId())
|
|
||||||
.clientSecret(oauth.getClientSecret())
|
|
||||||
.scope(oauth.getScopes())
|
|
||||||
.userNameAttributeName(oauth.getUseAsUsername())
|
|
||||||
.clientName("OIDC")
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@ConditionalOnProperty(
|
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
|
||||||
name = "security.saml2.enabled",
|
AuthenticationManagerBuilder authenticationManagerBuilder =
|
||||||
havingValue = "true",
|
http.getSharedObject(AuthenticationManagerBuilder.class);
|
||||||
matchIfMissing = false)
|
|
||||||
public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception {
|
|
||||||
|
|
||||||
SAML2 samlConf = applicationProperties.getSecurity().getSaml2();
|
// authenticationManagerBuilder =
|
||||||
|
// authenticationManagerBuilder.authenticationProvider(
|
||||||
|
// daoAuthenticationProvider()); // Benutzername/Passwort
|
||||||
|
|
||||||
Resource privateKeyResource = samlConf.getPrivateKey();
|
if (applicationProperties.getSecurity().getSaml() != null
|
||||||
|
&& applicationProperties.getSecurity().getSaml().getEnabled()) {
|
||||||
Resource certificateResource = samlConf.getSpCert();
|
authenticationManagerBuilder.authenticationProvider(
|
||||||
|
samlAuthenticationProvider()); // SAML
|
||||||
Saml2X509Credential signingCredential =
|
}
|
||||||
new Saml2X509Credential(
|
return authenticationManagerBuilder.build();
|
||||||
CertificateUtils.readPrivateKey(privateKeyResource),
|
|
||||||
CertificateUtils.readCertificate(certificateResource),
|
|
||||||
Saml2X509CredentialType.SIGNING);
|
|
||||||
|
|
||||||
X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getidpCert());
|
|
||||||
|
|
||||||
Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert);
|
|
||||||
|
|
||||||
RelyingPartyRegistration rp =
|
|
||||||
RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId())
|
|
||||||
.signingX509Credentials((c) -> c.add(signingCredential))
|
|
||||||
.assertingPartyDetails(
|
|
||||||
(details) ->
|
|
||||||
details.entityId(samlConf.getIdpIssuer())
|
|
||||||
.singleSignOnServiceLocation(
|
|
||||||
samlConf.getIdpSingleLoginUrl())
|
|
||||||
.verificationX509Credentials(
|
|
||||||
(c) -> c.add(verificationCredential))
|
|
||||||
.wantAuthnRequestsSigned(true))
|
|
||||||
.build();
|
|
||||||
return new InMemoryRelyingPartyRegistrationRepository(rp);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
public DaoAuthenticationProvider daoAuthenticationProvider() {
|
|
||||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
|
||||||
provider.setUserDetailsService(userDetailsService);
|
|
||||||
provider.setPasswordEncoder(passwordEncoder());
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
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) {
|
|
||||||
String useAsUsername =
|
|
||||||
applicationProperties
|
|
||||||
.getSecurity()
|
|
||||||
.getOauth2()
|
|
||||||
.getUseAsUsername();
|
|
||||||
Optional<User> userOpt =
|
|
||||||
userService.findByUsernameIgnoreCase(
|
|
||||||
(String) oauth2Auth.getAttributes().get(useAsUsername));
|
|
||||||
if (userOpt.isPresent()) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
if (user != null) {
|
|
||||||
mappedAuthorities.add(
|
|
||||||
new SimpleGrantedAuthority(
|
|
||||||
userService.findRole(user).getAuthority()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return mappedAuthorities;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import jakarta.servlet.FilterChain;
|
|||||||
import jakarta.servlet.ServletException;
|
import jakarta.servlet.ServletException;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
import stirling.software.SPDF.model.ApiKeyAuthenticationToken;
|
||||||
import stirling.software.SPDF.model.User;
|
import stirling.software.SPDF.model.User;
|
||||||
@@ -112,9 +111,7 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||||
response.getWriter()
|
response.getWriter()
|
||||||
.write(
|
.write(
|
||||||
"Authentication required. Please provide a X-API-KEY in request header.\n"
|
"Authentication required. Please provide a X-API-KEY in request header.\nThis is found in Settings -> Account Settings -> API Key\nAlternatively you can disable authentication if this is unexpected");
|
||||||
+ "This is found in Settings -> Account Settings -> API Key\n"
|
|
||||||
+ "Alternatively you can disable authentication if this is unexpected");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -127,8 +124,6 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
|||||||
username = ((UserDetails) principal).getUsername();
|
username = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
username = ((OAuth2User) principal).getName();
|
username = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
username = (String) principal;
|
username = (String) principal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
import stirling.software.SPDF.config.interfaces.DatabaseBackupInterface;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
@@ -45,6 +44,9 @@ public class UserService implements UserServiceInterface {
|
|||||||
|
|
||||||
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
@Autowired DatabaseBackupInterface databaseBackupHelper;
|
||||||
|
|
||||||
|
public long getTotalUserCount() {
|
||||||
|
return userRepository.count();
|
||||||
|
}
|
||||||
|
|
||||||
// Handle OAUTH2 login and user auto creation.
|
// Handle OAUTH2 login and user auto creation.
|
||||||
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
public boolean processOAuth2PostLogin(String username, boolean autoCreateUser)
|
||||||
@@ -336,10 +338,6 @@ public class UserService implements UserServiceInterface {
|
|||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
OAuth2User oAuth2User = (OAuth2User) principal;
|
OAuth2User oAuth2User = (OAuth2User) principal;
|
||||||
usernameP = oAuth2User.getName();
|
usernameP = oAuth2User.getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
CustomSaml2AuthenticatedPrincipal saml2User =
|
|
||||||
(CustomSaml2AuthenticatedPrincipal) principal;
|
|
||||||
usernameP = saml2User.getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
usernameP = (String) principal;
|
usernameP = (String) principal;
|
||||||
}
|
}
|
||||||
@@ -359,9 +357,4 @@ public class UserService implements UserServiceInterface {
|
|||||||
return principal.toString();
|
return principal.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public long getTotalUsersCount() {
|
|
||||||
return userRepository.count();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,8 @@ public class CustomOAuth2AuthenticationFailureHandler
|
|||||||
}
|
}
|
||||||
log.error("OAuth2 Authentication error: " + errorCode);
|
log.error("OAuth2 Authentication error: " + errorCode);
|
||||||
log.error("OAuth2AuthenticationException", exception);
|
log.error("OAuth2AuthenticationException", exception);
|
||||||
getRedirectStrategy().sendRedirect(request, response, "/login?erroroauth=" + errorCode);
|
getRedirectStrategy()
|
||||||
|
.sendRedirect(request, response, "/logout?erroroauth=" + errorCode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log.error("Unhandled authentication exception", exception);
|
log.error("Unhandled authentication exception", exception);
|
||||||
|
|||||||
@@ -75,11 +75,6 @@ public class CustomOAuth2AuthenticationSuccessHandler
|
|||||||
throw new LockedException(
|
throw new LockedException(
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
}
|
}
|
||||||
if (userService.isUserDisabled(username)) {
|
|
||||||
getRedirectStrategy()
|
|
||||||
.sendRedirect(request, response, "/logout?userIsDisabled=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (userService.usernameExistsIgnoreCase(username)
|
if (userService.usernameExistsIgnoreCase(username)
|
||||||
&& userService.hasPassword(username)
|
&& userService.hasPassword(username)
|
||||||
&& !userService.isAuthenticationTypeByUsername(
|
&& !userService.isAuthenticationTypeByUsername(
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
package stirling.software.SPDF.config.security.oauth2;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
|
||||||
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.Provider;
|
||||||
|
import stirling.software.SPDF.model.provider.UnsupportedProviderException;
|
||||||
|
import stirling.software.SPDF.utils.UrlUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CustomOAuth2LogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
|
private final ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public CustomOAuth2LogoutSuccessHandler(ApplicationProperties applicationProperties) {
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
String param = "logout=true";
|
||||||
|
String registrationId = null;
|
||||||
|
String issuer = null;
|
||||||
|
String clientId = null;
|
||||||
|
|
||||||
|
if (authentication == null) {
|
||||||
|
if (request.getParameter("userIsDisabled") != null) {
|
||||||
|
response.sendRedirect(
|
||||||
|
request.getContextPath() + "/login?erroroauth=userIsDisabled");
|
||||||
|
} else {
|
||||||
|
super.onLogoutSuccess(request, response, authentication);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
|
if (authentication instanceof OAuth2AuthenticationToken) {
|
||||||
|
OAuth2AuthenticationToken oauthToken = (OAuth2AuthenticationToken) authentication;
|
||||||
|
registrationId = oauthToken.getAuthorizedClientRegistrationId();
|
||||||
|
|
||||||
|
try {
|
||||||
|
Provider provider = oauth.getClient().get(registrationId);
|
||||||
|
issuer = provider.getIssuer();
|
||||||
|
clientId = provider.getClientId();
|
||||||
|
} catch (UnsupportedProviderException e) {
|
||||||
|
log.error(e.getMessage());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
registrationId = oauth.getProvider() != null ? oauth.getProvider() : "";
|
||||||
|
issuer = oauth.getIssuer();
|
||||||
|
clientId = oauth.getClientId();
|
||||||
|
}
|
||||||
|
String errorMessage = "";
|
||||||
|
if (request.getParameter("oauth2AuthenticationErrorWeb") != null) {
|
||||||
|
param = "erroroauth=oauth2AuthenticationErrorWeb";
|
||||||
|
} else if ((errorMessage = request.getParameter("error")) != null) {
|
||||||
|
param = "error=" + sanitizeInput(errorMessage);
|
||||||
|
} else if ((errorMessage = request.getParameter("erroroauth")) != null) {
|
||||||
|
param = "erroroauth=" + sanitizeInput(errorMessage);
|
||||||
|
} else if (request.getParameter("oauth2AutoCreateDisabled") != null) {
|
||||||
|
param = "error=oauth2AutoCreateDisabled";
|
||||||
|
} else if (request.getParameter("oauth2_admin_blocked_user") != null) {
|
||||||
|
param = "erroroauth=oauth2_admin_blocked_user";
|
||||||
|
} else if (request.getParameter("userIsDisabled") != null) {
|
||||||
|
param = "erroroauth=userIsDisabled";
|
||||||
|
} else if (request.getParameter("badcredentials") != null) {
|
||||||
|
param = "error=badcredentials";
|
||||||
|
}
|
||||||
|
|
||||||
|
String redirect_url = UrlUtils.getOrigin(request) + "/login?" + param;
|
||||||
|
|
||||||
|
switch (registrationId.toLowerCase()) {
|
||||||
|
case "keycloak":
|
||||||
|
// Add Keycloak specific logout URL if needed
|
||||||
|
String logoutUrl =
|
||||||
|
issuer
|
||||||
|
+ "/protocol/openid-connect/logout"
|
||||||
|
+ "?client_id="
|
||||||
|
+ clientId
|
||||||
|
+ "&post_logout_redirect_uri="
|
||||||
|
+ response.encodeRedirectURL(redirect_url);
|
||||||
|
log.info("Redirecting to Keycloak logout URL: " + logoutUrl);
|
||||||
|
response.sendRedirect(logoutUrl);
|
||||||
|
break;
|
||||||
|
case "github":
|
||||||
|
// Add GitHub specific logout URL if needed
|
||||||
|
String githubLogoutUrl = "https://github.com/logout";
|
||||||
|
log.info("Redirecting to GitHub logout URL: " + githubLogoutUrl);
|
||||||
|
response.sendRedirect(githubLogoutUrl);
|
||||||
|
break;
|
||||||
|
case "google":
|
||||||
|
// Add Google specific logout URL if needed
|
||||||
|
// String googleLogoutUrl =
|
||||||
|
// "https://accounts.google.com/Logout?continue=https://appengine.google.com/_ah/logout?continue="
|
||||||
|
// + response.encodeRedirectURL(redirect_url);
|
||||||
|
log.info("Google does not have a specific logout URL");
|
||||||
|
// log.info("Redirecting to Google logout URL: " + googleLogoutUrl);
|
||||||
|
// response.sendRedirect(googleLogoutUrl);
|
||||||
|
// break;
|
||||||
|
default:
|
||||||
|
String defaultRedirectUrl = request.getContextPath() + "/login?" + param;
|
||||||
|
log.info("Redirecting to default logout URL: " + defaultRedirectUrl);
|
||||||
|
response.sendRedirect(defaultRedirectUrl);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String sanitizeInput(String input) {
|
||||||
|
return input.replaceAll("[^a-zA-Z0-9 ]", "");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
import org.springframework.core.convert.converter.Converter;
|
||||||
|
import org.springframework.security.core.GrantedAuthority;
|
||||||
|
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class ConvertResponseToAuthentication
|
||||||
|
implements Converter<ResponseToken, Saml2Authentication> {
|
||||||
|
|
||||||
|
private final Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup;
|
||||||
|
|
||||||
|
public ConvertResponseToAuthentication(
|
||||||
|
Saml2AuthorityAttributeLookup saml2AuthorityAttributeLookup) {
|
||||||
|
this.saml2AuthorityAttributeLookup = saml2AuthorityAttributeLookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Saml2Authentication convert(ResponseToken responseToken) {
|
||||||
|
final Assertion assertion =
|
||||||
|
CollectionUtils.firstElement(responseToken.getResponse().getAssertions());
|
||||||
|
final Map<String, List<Object>> attributes =
|
||||||
|
SamlAssertionUtils.getAssertionAttributes(assertion);
|
||||||
|
final String registrationId =
|
||||||
|
responseToken.getToken().getRelyingPartyRegistration().getRegistrationId();
|
||||||
|
final ScimSaml2AuthenticatedPrincipal principal =
|
||||||
|
new ScimSaml2AuthenticatedPrincipal(
|
||||||
|
assertion,
|
||||||
|
attributes,
|
||||||
|
saml2AuthorityAttributeLookup.getIdentityMappings(registrationId));
|
||||||
|
final Collection<? extends GrantedAuthority> assertionAuthorities =
|
||||||
|
getAssertionAuthorities(
|
||||||
|
attributes,
|
||||||
|
saml2AuthorityAttributeLookup.getAuthorityAttribute(registrationId));
|
||||||
|
return new Saml2Authentication(
|
||||||
|
principal, responseToken.getToken().getSaml2Response(), assertionAuthorities);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Collection<? extends GrantedAuthority> getAssertionAuthorities(
|
||||||
|
final Map<String, List<Object>> attributes, final String authoritiesAttributeName) {
|
||||||
|
if (attributes == null || attributes.isEmpty()) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Object> groups = new ArrayList<>(attributes.get(authoritiesAttributeName));
|
||||||
|
return groups.stream()
|
||||||
|
.filter(String.class::isInstance)
|
||||||
|
.map(String.class::cast)
|
||||||
|
.map(String::toLowerCase)
|
||||||
|
.map(SimpleGrantedAuthority::new)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.BadCredentialsException;
|
||||||
|
import org.springframework.security.authentication.DisabledException;
|
||||||
|
import org.springframework.security.authentication.LockedException;
|
||||||
|
import org.springframework.security.core.AuthenticationException;
|
||||||
|
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
||||||
|
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CustomSAMLAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure(
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
AuthenticationException exception)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
if (exception instanceof BadCredentialsException) {
|
||||||
|
log.error("BadCredentialsException", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/login?error=badcredentials");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof DisabledException) {
|
||||||
|
log.error("User is deactivated: ", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/logout?userIsDisabled=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof LockedException) {
|
||||||
|
log.error("Account locked: ", exception);
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, "/logout?error=locked");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (exception instanceof Saml2AuthenticationException) {
|
||||||
|
log.error("SAML2 Authentication error: ", exception);
|
||||||
|
getRedirectStrategy()
|
||||||
|
.sendRedirect(request, response, "/logout?error=saml2AuthenticationError");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.error("Unhandled authentication exception", exception);
|
||||||
|
super.onAuthenticationFailure(request, response, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.authentication.LockedException;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
|
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
||||||
|
import org.springframework.security.web.savedrequest.SavedRequest;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import jakarta.servlet.http.HttpSession;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.config.security.LoginAttemptService;
|
||||||
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
|
import stirling.software.SPDF.utils.RequestUriUtils;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class CustomSAMLAuthenticationSuccessHandler
|
||||||
|
extends SavedRequestAwareAuthenticationSuccessHandler {
|
||||||
|
|
||||||
|
private LoginAttemptService loginAttemptService;
|
||||||
|
private UserService userService;
|
||||||
|
private ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
public CustomSAMLAuthenticationSuccessHandler(
|
||||||
|
LoginAttemptService loginAttemptService,
|
||||||
|
UserService userService,
|
||||||
|
ApplicationProperties applicationProperties) {
|
||||||
|
this.loginAttemptService = loginAttemptService;
|
||||||
|
this.userService = userService;
|
||||||
|
this.applicationProperties = applicationProperties;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws ServletException, IOException {
|
||||||
|
|
||||||
|
Object principal = authentication.getPrincipal();
|
||||||
|
String username = "";
|
||||||
|
|
||||||
|
if (principal instanceof OAuth2User) {
|
||||||
|
OAuth2User oauthUser = (OAuth2User) principal;
|
||||||
|
username = oauthUser.getName();
|
||||||
|
} else if (principal instanceof UserDetails) {
|
||||||
|
UserDetails oauthUser = (UserDetails) principal;
|
||||||
|
username = oauthUser.getUsername();
|
||||||
|
} else if (principal instanceof ScimSaml2AuthenticatedPrincipal) {
|
||||||
|
ScimSaml2AuthenticatedPrincipal samlPrincipal =
|
||||||
|
(ScimSaml2AuthenticatedPrincipal) principal;
|
||||||
|
username = samlPrincipal.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the saved request
|
||||||
|
HttpSession session = request.getSession(false);
|
||||||
|
String contextPath = request.getContextPath();
|
||||||
|
SavedRequest savedRequest =
|
||||||
|
(session != null)
|
||||||
|
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
||||||
|
: null;
|
||||||
|
|
||||||
|
if (savedRequest != null
|
||||||
|
&& !RequestUriUtils.isStaticResource(contextPath, savedRequest.getRedirectUrl())) {
|
||||||
|
// Redirect to the original destination
|
||||||
|
super.onAuthenticationSuccess(request, response, authentication);
|
||||||
|
} else {
|
||||||
|
OAUTH2 oAuth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
|
if (loginAttemptService.isBlocked(username)) {
|
||||||
|
if (session != null) {
|
||||||
|
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
||||||
|
}
|
||||||
|
throw new LockedException(
|
||||||
|
"Your account has been locked due to too many failed login attempts.");
|
||||||
|
}
|
||||||
|
if (userService.usernameExistsIgnoreCase(username)
|
||||||
|
&& userService.hasPassword(username)
|
||||||
|
&& !userService.isAuthenticationTypeByUsername(
|
||||||
|
username, AuthenticationType.OAUTH2)
|
||||||
|
&& oAuth.getAutoCreateUser()) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (oAuth.getBlockRegistration()
|
||||||
|
&& !userService.usernameExistsIgnoreCase(username)) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?oauth2_admin_blocked_user=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (principal instanceof OAuth2User) {
|
||||||
|
userService.processOAuth2PostLogin(username, oAuth.getAutoCreateUser());
|
||||||
|
}
|
||||||
|
response.sendRedirect(contextPath + "/");
|
||||||
|
return;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
|
||||||
|
|
||||||
|
import jakarta.servlet.ServletException;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class SAMLLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onLogoutSuccess(
|
||||||
|
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
||||||
|
throws IOException, ServletException {
|
||||||
|
|
||||||
|
String redirectUrl = determineTargetUrl(request, response, authentication);
|
||||||
|
|
||||||
|
if (response.isCommitted()) {
|
||||||
|
log.debug("Response has already been committed. Unable to redirect to " + redirectUrl);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRedirectStrategy().sendRedirect(request, response, redirectUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String determineTargetUrl(
|
||||||
|
HttpServletRequest request,
|
||||||
|
HttpServletResponse response,
|
||||||
|
Authentication authentication) {
|
||||||
|
// Default to the root URL
|
||||||
|
return "/";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
public interface Saml2AuthorityAttributeLookup {
|
||||||
|
String getAuthorityAttribute(String registrationId);
|
||||||
|
|
||||||
|
SimpleScimMappings getIdentityMappings(String registrationId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
public class Saml2AuthorityAttributeLookupImpl implements Saml2AuthorityAttributeLookup {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAuthorityAttribute(String registrationId) {
|
||||||
|
return "authorityAttributeName";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SimpleScimMappings getIdentityMappings(String registrationId) {
|
||||||
|
return new SimpleScimMappings();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import org.opensaml.core.xml.XMLObject;
|
||||||
|
import org.opensaml.core.xml.schema.*;
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
|
||||||
|
public class SamlAssertionUtils {
|
||||||
|
|
||||||
|
public static Map<String, List<Object>> getAssertionAttributes(Assertion assertion) {
|
||||||
|
Map<String, List<Object>> attributeMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
assertion
|
||||||
|
.getAttributeStatements()
|
||||||
|
.forEach(
|
||||||
|
attributeStatement -> {
|
||||||
|
attributeStatement
|
||||||
|
.getAttributes()
|
||||||
|
.forEach(
|
||||||
|
attribute -> {
|
||||||
|
List<Object> attributeValues = new ArrayList<>();
|
||||||
|
|
||||||
|
attribute
|
||||||
|
.getAttributeValues()
|
||||||
|
.forEach(
|
||||||
|
xmlObject -> {
|
||||||
|
Object attributeValue =
|
||||||
|
getXmlObjectValue(
|
||||||
|
xmlObject);
|
||||||
|
if (attributeValue != null) {
|
||||||
|
attributeValues.add(
|
||||||
|
attributeValue);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
attributeMap.put(
|
||||||
|
attribute.getName(), attributeValues);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return attributeMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Object getXmlObjectValue(XMLObject xmlObject) {
|
||||||
|
if (xmlObject instanceof XSAny) {
|
||||||
|
return ((XSAny) xmlObject).getTextContent();
|
||||||
|
} else if (xmlObject instanceof XSString) {
|
||||||
|
return ((XSString) xmlObject).getValue();
|
||||||
|
} else if (xmlObject instanceof XSInteger) {
|
||||||
|
return ((XSInteger) xmlObject).getValue();
|
||||||
|
} else if (xmlObject instanceof XSURI) {
|
||||||
|
return ((XSURI) xmlObject).getURI();
|
||||||
|
} else if (xmlObject instanceof XSBoolean) {
|
||||||
|
return ((XSBoolean) xmlObject).getValue().getValue();
|
||||||
|
} else if (xmlObject instanceof XSDateTime) {
|
||||||
|
Instant dateTime = ((XSDateTime) xmlObject).getValue();
|
||||||
|
return (dateTime != null) ? Instant.ofEpochMilli(dateTime.toEpochMilli()) : null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
|
||||||
|
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrations;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@Slf4j
|
||||||
|
public class SamlConfig {
|
||||||
|
|
||||||
|
@Autowired ApplicationProperties applicationProperties;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@ConditionalOnProperty(
|
||||||
|
value = "security.saml.enabled",
|
||||||
|
havingValue = "true",
|
||||||
|
matchIfMissing = false)
|
||||||
|
public RelyingPartyRegistrationRepository relyingPartyRegistrationRepository()
|
||||||
|
throws CertificateException {
|
||||||
|
RelyingPartyRegistration registration =
|
||||||
|
RelyingPartyRegistrations.fromMetadataLocation(
|
||||||
|
applicationProperties
|
||||||
|
.getSecurity()
|
||||||
|
.getSaml()
|
||||||
|
.getIdpMetadataLocation())
|
||||||
|
.entityId(applicationProperties.getSecurity().getSaml().getEntityId())
|
||||||
|
.registrationId(
|
||||||
|
applicationProperties.getSecurity().getSaml().getRegistrationId())
|
||||||
|
.build();
|
||||||
|
return new InMemoryRelyingPartyRegistrationRepository(registration);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import org.opensaml.saml.saml2.core.Assertion;
|
||||||
|
import org.springframework.security.core.AuthenticatedPrincipal;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import com.unboundid.scim2.common.types.Email;
|
||||||
|
import com.unboundid.scim2.common.types.Name;
|
||||||
|
import com.unboundid.scim2.common.types.UserResource;
|
||||||
|
|
||||||
|
public class ScimSaml2AuthenticatedPrincipal implements AuthenticatedPrincipal, Serializable {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
private final transient UserResource userResource;
|
||||||
|
|
||||||
|
public ScimSaml2AuthenticatedPrincipal(
|
||||||
|
final Assertion assertion,
|
||||||
|
final Map<String, List<Object>> attributes,
|
||||||
|
final SimpleScimMappings attributeMappings) {
|
||||||
|
Assert.notNull(assertion, "assertion cannot be null");
|
||||||
|
Assert.notNull(assertion.getSubject(), "assertion subject cannot be null");
|
||||||
|
Assert.notNull(
|
||||||
|
assertion.getSubject().getNameID(), "assertion subject NameID cannot be null");
|
||||||
|
Assert.notNull(attributes, "attributes cannot be null");
|
||||||
|
Assert.notNull(attributeMappings, "attributeMappings cannot be null");
|
||||||
|
|
||||||
|
final Name name =
|
||||||
|
new Name()
|
||||||
|
.setFamilyName(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getFamilyName))
|
||||||
|
.setGivenName(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getGivenName));
|
||||||
|
|
||||||
|
final List<Email> emails = new ArrayList<>(1);
|
||||||
|
emails.add(
|
||||||
|
new Email()
|
||||||
|
.setValue(
|
||||||
|
getAttribute(
|
||||||
|
attributes,
|
||||||
|
attributeMappings,
|
||||||
|
SimpleScimMappings::getEmail))
|
||||||
|
.setPrimary(true));
|
||||||
|
|
||||||
|
userResource =
|
||||||
|
new UserResource()
|
||||||
|
.setUserName(assertion.getSubject().getNameID().getValue())
|
||||||
|
.setName(name)
|
||||||
|
.setEmails(emails);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAttribute(
|
||||||
|
final Map<String, List<Object>> attributes,
|
||||||
|
final SimpleScimMappings simpleScimMappings,
|
||||||
|
final Function<SimpleScimMappings, String> attributeMapper) {
|
||||||
|
|
||||||
|
final String key = attributeMapper.apply(simpleScimMappings);
|
||||||
|
|
||||||
|
final List<Object> values = attributes.getOrDefault(key, Collections.emptyList());
|
||||||
|
|
||||||
|
return values.stream()
|
||||||
|
.filter(String.class::isInstance)
|
||||||
|
.map(String.class::cast)
|
||||||
|
.findFirst()
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return this.userResource.getUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserResource getUserResource() {
|
||||||
|
return this.userResource;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
package stirling.software.SPDF.config.security.saml;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class SimpleScimMappings {
|
||||||
|
String givenName;
|
||||||
|
String familyName;
|
||||||
|
String email;
|
||||||
|
}
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
|
||||||
import java.security.spec.PKCS8EncodedKeySpec;
|
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
|
||||||
import org.springframework.util.FileCopyUtils;
|
|
||||||
|
|
||||||
public class CertificateUtils {
|
|
||||||
|
|
||||||
public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
|
|
||||||
String certificateString =
|
|
||||||
new String(
|
|
||||||
FileCopyUtils.copyToByteArray(certificateResource.getInputStream()),
|
|
||||||
StandardCharsets.UTF_8);
|
|
||||||
String certContent =
|
|
||||||
certificateString
|
|
||||||
.replace("-----BEGIN CERTIFICATE-----", "")
|
|
||||||
.replace("-----END CERTIFICATE-----", "")
|
|
||||||
.replaceAll("\\R", "")
|
|
||||||
.replaceAll("\\s+", "");
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
|
||||||
byte[] decodedCert = Base64.getDecoder().decode(certContent);
|
|
||||||
return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decodedCert));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
|
|
||||||
String privateKeyString =
|
|
||||||
new String(
|
|
||||||
FileCopyUtils.copyToByteArray(privateKeyResource.getInputStream()),
|
|
||||||
StandardCharsets.UTF_8);
|
|
||||||
String privateKeyContent =
|
|
||||||
privateKeyString
|
|
||||||
.replace("-----BEGIN PRIVATE KEY-----", "")
|
|
||||||
.replace("-----END PRIVATE KEY-----", "")
|
|
||||||
.replaceAll("\\R", "")
|
|
||||||
.replaceAll("\\s+", "");
|
|
||||||
KeyFactory kf = KeyFactory.getInstance("RSA");
|
|
||||||
byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent);
|
|
||||||
return (RSAPrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
|
|
||||||
|
|
||||||
public class CustomSaml2AuthenticatedPrincipal
|
|
||||||
implements Saml2AuthenticatedPrincipal, Serializable {
|
|
||||||
|
|
||||||
private final String name;
|
|
||||||
private final Map<String, List<Object>> attributes;
|
|
||||||
private final String nameId;
|
|
||||||
private final List<String> sessionIndexes;
|
|
||||||
|
|
||||||
public CustomSaml2AuthenticatedPrincipal(
|
|
||||||
String name,
|
|
||||||
Map<String, List<Object>> attributes,
|
|
||||||
String nameId,
|
|
||||||
List<String> sessionIndexes) {
|
|
||||||
this.name = name;
|
|
||||||
this.attributes = attributes;
|
|
||||||
this.nameId = nameId;
|
|
||||||
this.sessionIndexes = sessionIndexes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Map<String, List<Object>> getAttributes() {
|
|
||||||
return this.attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNameId() {
|
|
||||||
return this.nameId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<String> getSessionIndexes() {
|
|
||||||
return this.sessionIndexes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.ProviderNotFoundException;
|
|
||||||
import org.springframework.security.core.AuthenticationException;
|
|
||||||
import org.springframework.security.saml2.core.Saml2Error;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
|
|
||||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
|
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
@Slf4j
|
|
||||||
public class CustomSaml2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationFailure(
|
|
||||||
HttpServletRequest request,
|
|
||||||
HttpServletResponse response,
|
|
||||||
AuthenticationException exception)
|
|
||||||
throws IOException, ServletException {
|
|
||||||
if (exception instanceof Saml2AuthenticationException) {
|
|
||||||
Saml2Error error = ((Saml2AuthenticationException) exception).getSaml2Error();
|
|
||||||
getRedirectStrategy()
|
|
||||||
.sendRedirect(request, response, "/login?erroroauth=" + error.getErrorCode());
|
|
||||||
} else if (exception instanceof ProviderNotFoundException) {
|
|
||||||
getRedirectStrategy()
|
|
||||||
.sendRedirect(
|
|
||||||
request,
|
|
||||||
response,
|
|
||||||
"/login?erroroauth=not_authentication_provider_found");
|
|
||||||
}
|
|
||||||
log.error("AuthenticationException: " + exception);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,91 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
import org.springframework.security.authentication.LockedException;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
|
|
||||||
import org.springframework.security.web.savedrequest.SavedRequest;
|
|
||||||
|
|
||||||
import jakarta.servlet.ServletException;
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
|
||||||
import jakarta.servlet.http.HttpSession;
|
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import stirling.software.SPDF.config.security.LoginAttemptService;
|
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
|
||||||
import stirling.software.SPDF.utils.RequestUriUtils;
|
|
||||||
|
|
||||||
@AllArgsConstructor
|
|
||||||
public class CustomSaml2AuthenticationSuccessHandler
|
|
||||||
extends SavedRequestAwareAuthenticationSuccessHandler {
|
|
||||||
|
|
||||||
private LoginAttemptService loginAttemptService;
|
|
||||||
|
|
||||||
private ApplicationProperties applicationProperties;
|
|
||||||
private UserService userService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationSuccess(
|
|
||||||
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
|
|
||||||
throws ServletException, IOException {
|
|
||||||
|
|
||||||
Object principal = authentication.getPrincipal();
|
|
||||||
|
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
String username = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
// Get the saved request
|
|
||||||
HttpSession session = request.getSession(false);
|
|
||||||
String contextPath = request.getContextPath();
|
|
||||||
SavedRequest savedRequest =
|
|
||||||
(session != null)
|
|
||||||
? (SavedRequest) session.getAttribute("SPRING_SECURITY_SAVED_REQUEST")
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (savedRequest != null
|
|
||||||
&& !RequestUriUtils.isStaticResource(
|
|
||||||
contextPath, savedRequest.getRedirectUrl())) {
|
|
||||||
// Redirect to the original destination
|
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
} else {
|
|
||||||
SAML2 saml2 = applicationProperties.getSecurity().getSaml2();
|
|
||||||
|
|
||||||
if (loginAttemptService.isBlocked(username)) {
|
|
||||||
if (session != null) {
|
|
||||||
session.removeAttribute("SPRING_SECURITY_SAVED_REQUEST");
|
|
||||||
}
|
|
||||||
throw new LockedException(
|
|
||||||
"Your account has been locked due to too many failed login attempts.");
|
|
||||||
}
|
|
||||||
if (userService.usernameExistsIgnoreCase(username)
|
|
||||||
&& userService.hasPassword(username)
|
|
||||||
&& !userService.isAuthenticationTypeByUsername(
|
|
||||||
username, AuthenticationType.OAUTH2)
|
|
||||||
&& saml2.getAutoCreateUser()) {
|
|
||||||
response.sendRedirect(
|
|
||||||
contextPath + "/logout?oauth2AuthenticationErrorWeb=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (saml2.getBlockRegistration()
|
|
||||||
&& !userService.usernameExistsIgnoreCase(username)) {
|
|
||||||
response.sendRedirect(
|
|
||||||
contextPath + "/login?erroroauth=oauth2_admin_blocked_user");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
userService.processOAuth2PostLogin(username, saml2.getAutoCreateUser());
|
|
||||||
response.sendRedirect(contextPath + "/");
|
|
||||||
return;
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
response.sendRedirect(contextPath + "/logout?invalidUsername=true");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
super.onAuthenticationSuccess(request, response, authentication);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
package stirling.software.SPDF.config.security.saml2;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import org.opensaml.core.xml.XMLObject;
|
|
||||||
import org.opensaml.core.xml.schema.XSBoolean;
|
|
||||||
import org.opensaml.core.xml.schema.XSString;
|
|
||||||
import org.opensaml.saml.saml2.core.Assertion;
|
|
||||||
import org.opensaml.saml.saml2.core.Attribute;
|
|
||||||
import org.opensaml.saml.saml2.core.AttributeStatement;
|
|
||||||
import org.opensaml.saml.saml2.core.AuthnStatement;
|
|
||||||
import org.springframework.core.convert.converter.Converter;
|
|
||||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.OpenSaml4AuthenticationProvider.ResponseToken;
|
|
||||||
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
|
||||||
import stirling.software.SPDF.model.User;
|
|
||||||
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class CustomSaml2ResponseAuthenticationConverter
|
|
||||||
implements Converter<ResponseToken, Saml2Authentication> {
|
|
||||||
|
|
||||||
private UserService userService;
|
|
||||||
|
|
||||||
public CustomSaml2ResponseAuthenticationConverter(UserService userService) {
|
|
||||||
this.userService = userService;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Saml2Authentication convert(ResponseToken responseToken) {
|
|
||||||
// Extract the assertion from the response
|
|
||||||
Assertion assertion = responseToken.getResponse().getAssertions().get(0);
|
|
||||||
|
|
||||||
// Extract the NameID
|
|
||||||
String nameId = assertion.getSubject().getNameID().getValue();
|
|
||||||
|
|
||||||
Optional<User> userOpt = userService.findByUsernameIgnoreCase(nameId);
|
|
||||||
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority("ROLE_USER");
|
|
||||||
if (userOpt.isPresent()) {
|
|
||||||
User user = userOpt.get();
|
|
||||||
if (user != null) {
|
|
||||||
simpleGrantedAuthority =
|
|
||||||
new SimpleGrantedAuthority(userService.findRole(user).getAuthority());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the SessionIndexes
|
|
||||||
List<String> sessionIndexes = new ArrayList<>();
|
|
||||||
for (AuthnStatement authnStatement : assertion.getAuthnStatements()) {
|
|
||||||
sessionIndexes.add(authnStatement.getSessionIndex());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract the Attributes
|
|
||||||
Map<String, List<Object>> attributes = extractAttributes(assertion);
|
|
||||||
|
|
||||||
// Create the custom principal
|
|
||||||
CustomSaml2AuthenticatedPrincipal principal =
|
|
||||||
new CustomSaml2AuthenticatedPrincipal(nameId, attributes, nameId, sessionIndexes);
|
|
||||||
|
|
||||||
// Create the Saml2Authentication
|
|
||||||
return new Saml2Authentication(
|
|
||||||
principal,
|
|
||||||
responseToken.getToken().getSaml2Response(),
|
|
||||||
Collections.singletonList(simpleGrantedAuthority));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, List<Object>> extractAttributes(Assertion assertion) {
|
|
||||||
Map<String, List<Object>> attributes = new HashMap<>();
|
|
||||||
for (AttributeStatement attributeStatement : assertion.getAttributeStatements()) {
|
|
||||||
for (Attribute attribute : attributeStatement.getAttributes()) {
|
|
||||||
String attributeName = attribute.getName();
|
|
||||||
List<Object> values = new ArrayList<>();
|
|
||||||
for (XMLObject xmlObject : attribute.getAttributeValues()) {
|
|
||||||
log.info("BOOL: " + ((XSBoolean) xmlObject).getValue());
|
|
||||||
values.add(((XSString) xmlObject).getValue());
|
|
||||||
}
|
|
||||||
attributes.put(attributeName, values);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ import org.springframework.security.oauth2.core.user.OAuth2User;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import jakarta.transaction.Transactional;
|
import jakarta.transaction.Transactional;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.model.SessionEntity;
|
import stirling.software.SPDF.model.SessionEntity;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -51,8 +50,6 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
principalName = ((UserDetails) principal).getUsername();
|
principalName = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
principalName = (String) principal;
|
principalName = (String) principal;
|
||||||
}
|
}
|
||||||
@@ -82,8 +79,6 @@ public class SessionPersistentRegistry implements SessionRegistry {
|
|||||||
principalName = ((UserDetails) principal).getUsername();
|
principalName = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
principalName = ((OAuth2User) principal).getName();
|
principalName = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
principalName = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
principalName = (String) principal;
|
principalName = (String) principal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import org.springframework.http.ResponseEntity;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
||||||
|
|
||||||
import stirling.software.SPDF.model.api.PDFFile;
|
import stirling.software.SPDF.model.api.PDFFile;
|
||||||
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
import stirling.software.SPDF.service.CustomPDDocumentFactory;
|
||||||
@@ -22,7 +21,6 @@ import stirling.software.SPDF.utils.WebResponseUtils;
|
|||||||
*/
|
*/
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/v1/general")
|
@RequestMapping("/api/v1/general")
|
||||||
@Tag(name = "General", description = "General APIs")
|
|
||||||
public class PdfImageRemovalController {
|
public class PdfImageRemovalController {
|
||||||
|
|
||||||
// Service for removing images from PDFs
|
// Service for removing images from PDFs
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ public class ScalePagesController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
private PDRectangle getTargetSize(String targetPDRectangle, PDDocument sourceDocument) {
|
||||||
if ("KEEP".equals(targetPDRectangle)) {
|
if (targetPDRectangle.equals("KEEP")) {
|
||||||
if (sourceDocument.getNumberOfPages() == 0) {
|
if (sourceDocument.getNumberOfPages() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ public class SettingsController {
|
|||||||
}
|
}
|
||||||
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
GeneralUtils.saveKeyToConfig("system.enableAnalytics", String.valueOf(enabled), false);
|
||||||
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
applicationProperties.getSystem().setEnableAnalytics(String.valueOf(enabled));
|
||||||
|
|
||||||
return ResponseEntity.ok("Updated");
|
return ResponseEntity.ok("Updated");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.UserService;
|
import stirling.software.SPDF.config.security.UserService;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.AuthenticationType;
|
import stirling.software.SPDF.model.AuthenticationType;
|
||||||
import stirling.software.SPDF.model.Role;
|
import stirling.software.SPDF.model.Role;
|
||||||
@@ -337,8 +336,6 @@ public class UserController {
|
|||||||
userNameP = ((UserDetails) principal).getUsername();
|
userNameP = ((UserDetails) principal).getUsername();
|
||||||
} else if (principal instanceof OAuth2User) {
|
} else if (principal instanceof OAuth2User) {
|
||||||
userNameP = ((OAuth2User) principal).getName();
|
userNameP = ((OAuth2User) principal).getName();
|
||||||
} else if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
userNameP = ((CustomSaml2AuthenticatedPrincipal) principal).getName();
|
|
||||||
} else if (principal instanceof String) {
|
} else if (principal instanceof String) {
|
||||||
userNameP = (String) principal;
|
userNameP = (String) principal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ public class ConvertImgPDFController {
|
|||||||
result =
|
result =
|
||||||
PdfUtils.convertFromPdf(
|
PdfUtils.convertFromPdf(
|
||||||
pdfBytes,
|
pdfBytes,
|
||||||
"webp".equalsIgnoreCase(imageFormat) ? "png" : imageFormat.toUpperCase(),
|
imageFormat.equalsIgnoreCase("webp") ? "png" : imageFormat.toUpperCase(),
|
||||||
colorTypeResult,
|
colorTypeResult,
|
||||||
singleImage,
|
singleImage,
|
||||||
Integer.valueOf(dpi),
|
Integer.valueOf(dpi),
|
||||||
@@ -90,9 +90,9 @@ public class ConvertImgPDFController {
|
|||||||
if (result == null || result.length == 0) {
|
if (result == null || result.length == 0) {
|
||||||
logger.error("resultant bytes for {} is null, error converting ", filename);
|
logger.error("resultant bytes for {} is null, error converting ", filename);
|
||||||
}
|
}
|
||||||
if ("webp".equalsIgnoreCase(imageFormat) && !CheckProgramInstall.isPythonAvailable()) {
|
if (imageFormat.equalsIgnoreCase("webp") && !CheckProgramInstall.isPythonAvailable()) {
|
||||||
throw new IOException("Python is not installed. Required for WebP conversion.");
|
throw new IOException("Python is not installed. Required for WebP conversion.");
|
||||||
} else if ("webp".equalsIgnoreCase(imageFormat)
|
} else if (imageFormat.equalsIgnoreCase("webp")
|
||||||
&& CheckProgramInstall.isPythonAvailable()) {
|
&& CheckProgramInstall.isPythonAvailable()) {
|
||||||
// Write the output stream to a temp file
|
// Write the output stream to a temp file
|
||||||
Path tempFile = Files.createTempFile("temp_png", ".png");
|
Path tempFile = Files.createTempFile("temp_png", ".png");
|
||||||
|
|||||||
@@ -4,6 +4,4 @@ public interface UserServiceInterface {
|
|||||||
String getApiKeyForUser(String username);
|
String getApiKeyForUser(String username);
|
||||||
|
|
||||||
String getCurrentUsername();
|
String getCurrentUsername();
|
||||||
|
|
||||||
long getTotalUsersCount();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,13 +21,10 @@ import io.swagger.v3.oas.annotations.tags.Tag;
|
|||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import stirling.software.SPDF.config.security.saml2.CustomSaml2AuthenticatedPrincipal;
|
|
||||||
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
import stirling.software.SPDF.config.security.session.SessionPersistentRegistry;
|
||||||
import stirling.software.SPDF.model.*;
|
import stirling.software.SPDF.model.*;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
import stirling.software.SPDF.model.ApplicationProperties.Security.OAUTH2.Client;
|
||||||
import stirling.software.SPDF.model.ApplicationProperties.Security.SAML2;
|
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
import stirling.software.SPDF.model.provider.GoogleProvider;
|
import stirling.software.SPDF.model.provider.GoogleProvider;
|
||||||
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
import stirling.software.SPDF.model.provider.KeycloakProvider;
|
||||||
@@ -54,44 +51,27 @@ public class AccountWebController {
|
|||||||
|
|
||||||
Map<String, String> providerList = new HashMap<>();
|
Map<String, String> providerList = new HashMap<>();
|
||||||
|
|
||||||
Security securityProps = applicationProperties.getSecurity();
|
OAUTH2 oauth = applicationProperties.getSecurity().getOauth2();
|
||||||
|
|
||||||
OAUTH2 oauth = securityProps.getOauth2();
|
|
||||||
if (oauth != null) {
|
if (oauth != null) {
|
||||||
if (oauth.getEnabled()) {
|
if (oauth.isSettingsValid()) {
|
||||||
if (oauth.isSettingsValid()) {
|
providerList.put("oidc", oauth.getProvider());
|
||||||
providerList.put("/oauth2/authorization/oidc", oauth.getProvider());
|
|
||||||
}
|
|
||||||
Client client = oauth.getClient();
|
|
||||||
if (client != null) {
|
|
||||||
GoogleProvider google = client.getGoogle();
|
|
||||||
if (google.isSettingsValid()) {
|
|
||||||
providerList.put(
|
|
||||||
"/oauth2/authorization/" + google.getName(),
|
|
||||||
google.getClientName());
|
|
||||||
}
|
|
||||||
|
|
||||||
GithubProvider github = client.getGithub();
|
|
||||||
if (github.isSettingsValid()) {
|
|
||||||
providerList.put(
|
|
||||||
"/oauth2/authorization/" + github.getName(),
|
|
||||||
github.getClientName());
|
|
||||||
}
|
|
||||||
|
|
||||||
KeycloakProvider keycloak = client.getKeycloak();
|
|
||||||
if (keycloak.isSettingsValid()) {
|
|
||||||
providerList.put(
|
|
||||||
"/oauth2/authorization/" + keycloak.getName(),
|
|
||||||
keycloak.getClientName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
Client client = oauth.getClient();
|
||||||
|
if (client != null) {
|
||||||
|
GoogleProvider google = client.getGoogle();
|
||||||
|
if (google.isSettingsValid()) {
|
||||||
|
providerList.put(google.getName(), google.getClientName());
|
||||||
|
}
|
||||||
|
|
||||||
SAML2 saml2 = securityProps.getSaml2();
|
GithubProvider github = client.getGithub();
|
||||||
if (saml2 != null) {
|
if (github.isSettingsValid()) {
|
||||||
if (saml2.getEnabled()) {
|
providerList.put(github.getName(), github.getClientName());
|
||||||
providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2");
|
}
|
||||||
|
|
||||||
|
KeycloakProvider keycloak = client.getKeycloak();
|
||||||
|
if (keycloak.isSettingsValid()) {
|
||||||
|
providerList.put(keycloak.getName(), keycloak.getClientName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove any null keys/values from the providerList
|
// Remove any null keys/values from the providerList
|
||||||
@@ -100,8 +80,9 @@ public class AccountWebController {
|
|||||||
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
.removeIf(entry -> entry.getKey() == null || entry.getValue() == null);
|
||||||
model.addAttribute("providerlist", providerList);
|
model.addAttribute("providerlist", providerList);
|
||||||
|
|
||||||
model.addAttribute("loginMethod", securityProps.getLoginMethod());
|
model.addAttribute("loginMethod", applicationProperties.getSecurity().getLoginMethod());
|
||||||
model.addAttribute("altLogin", securityProps.isAltLogin());
|
model.addAttribute(
|
||||||
|
"oAuth2Enabled", applicationProperties.getSecurity().getOauth2().getEnabled());
|
||||||
|
|
||||||
model.addAttribute("currentPage", "login");
|
model.addAttribute("currentPage", "login");
|
||||||
|
|
||||||
@@ -368,17 +349,6 @@ public class AccountWebController {
|
|||||||
// Add oAuth2 Login attributes to the model
|
// Add oAuth2 Login attributes to the model
|
||||||
model.addAttribute("oAuth2Login", true);
|
model.addAttribute("oAuth2Login", true);
|
||||||
}
|
}
|
||||||
if (principal instanceof CustomSaml2AuthenticatedPrincipal) {
|
|
||||||
// Cast the principal object to OAuth2User
|
|
||||||
CustomSaml2AuthenticatedPrincipal userDetails =
|
|
||||||
(CustomSaml2AuthenticatedPrincipal) principal;
|
|
||||||
|
|
||||||
// Retrieve username and other attributes
|
|
||||||
username = userDetails.getName();
|
|
||||||
// Add oAuth2 Login attributes to the model
|
|
||||||
model.addAttribute("oAuth2Login", true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (username != null) {
|
if (username != null) {
|
||||||
// Fetch user details from the database
|
// Fetch user details from the database
|
||||||
Optional<User> user =
|
Optional<User> user =
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
package stirling.software.SPDF.model;
|
package stirling.software.SPDF.model;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.PropertySource;
|
import org.springframework.context.annotation.PropertySource;
|
||||||
@@ -22,8 +18,6 @@ import org.springframework.core.io.FileSystemResource;
|
|||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.Setter;
|
|
||||||
import lombok.ToString;
|
import lombok.ToString;
|
||||||
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
import stirling.software.SPDF.config.YamlPropertySourceFactory;
|
||||||
import stirling.software.SPDF.model.provider.GithubProvider;
|
import stirling.software.SPDF.model.provider.GithubProvider;
|
||||||
@@ -47,6 +41,7 @@ public class ApplicationProperties {
|
|||||||
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
|
private AutomaticallyGenerated automaticallyGenerated = new AutomaticallyGenerated();
|
||||||
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
|
private EnterpriseEdition enterpriseEdition = new EnterpriseEdition();
|
||||||
private AutoPipeline autoPipeline = new AutoPipeline();
|
private AutoPipeline autoPipeline = new AutoPipeline();
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ApplicationProperties.class);
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class AutoPipeline {
|
public static class AutoPipeline {
|
||||||
@@ -68,108 +63,41 @@ public class ApplicationProperties {
|
|||||||
private Boolean csrfDisabled;
|
private Boolean csrfDisabled;
|
||||||
private InitialLogin initialLogin = new InitialLogin();
|
private InitialLogin initialLogin = new InitialLogin();
|
||||||
private OAUTH2 oauth2 = new OAUTH2();
|
private OAUTH2 oauth2 = new OAUTH2();
|
||||||
private SAML2 saml2 = new SAML2();
|
private SAML saml = new SAML();
|
||||||
private int loginAttemptCount;
|
private int loginAttemptCount;
|
||||||
private long loginResetTimeMinutes;
|
private long loginResetTimeMinutes;
|
||||||
private String loginMethod = "all";
|
private String loginMethod = "all";
|
||||||
|
|
||||||
public Boolean isAltLogin() {
|
|
||||||
return saml2.getEnabled() || oauth2.getEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum LoginMethods {
|
|
||||||
ALL("all"),
|
|
||||||
NORMAL("normal"),
|
|
||||||
OAUTH2("oauth2"),
|
|
||||||
SAML2("saml2");
|
|
||||||
|
|
||||||
private String method;
|
|
||||||
|
|
||||||
LoginMethods(String method) {
|
|
||||||
this.method = method;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return method;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUserPass() {
|
|
||||||
return (loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString())
|
|
||||||
|| loginMethod.equalsIgnoreCase(LoginMethods.ALL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOauth2Activ() {
|
|
||||||
return (oauth2 != null
|
|
||||||
&& oauth2.getEnabled()
|
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSaml2Activ() {
|
|
||||||
return (saml2 != null
|
|
||||||
&& saml2.getEnabled()
|
|
||||||
&& !loginMethod.equalsIgnoreCase(LoginMethods.NORMAL.toString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class InitialLogin {
|
public static class InitialLogin {
|
||||||
private String username;
|
private String username;
|
||||||
@ToString.Exclude private String password;
|
@ToString.Exclude private String password;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Getter
|
@Data
|
||||||
@Setter
|
public static class SAML {
|
||||||
public static class SAML2 {
|
|
||||||
private Boolean enabled = false;
|
private Boolean enabled = false;
|
||||||
private Boolean autoCreateUser = false;
|
private String entityId;
|
||||||
private Boolean blockRegistration = false;
|
private String registrationId;
|
||||||
private String registrationId = "stirling";
|
private String spBaseUrl;
|
||||||
private String idpMetadataUri;
|
private String idpMetadataLocation;
|
||||||
private String idpSingleLogoutUrl;
|
private KeyStore keystore;
|
||||||
private String idpSingleLoginUrl;
|
|
||||||
private String idpIssuer;
|
|
||||||
private String idpCert;
|
|
||||||
private String privateKey;
|
|
||||||
private String spCert;
|
|
||||||
|
|
||||||
public InputStream getIdpMetadataUri() throws IOException {
|
@Data
|
||||||
if (idpMetadataUri.startsWith("classpath:")) {
|
public static class KeyStore {
|
||||||
return new ClassPathResource(idpMetadataUri.substring("classpath".length()))
|
private String keystoreLocation;
|
||||||
.getInputStream();
|
private String keystorePassword;
|
||||||
}
|
private String keyAlias;
|
||||||
try {
|
private String keyPassword;
|
||||||
URI uri = new URI(idpMetadataUri);
|
private String realmCertificateAlias;
|
||||||
URL url = uri.toURL();
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
|
||||||
connection.setRequestMethod("GET");
|
|
||||||
return connection.getInputStream();
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
throw new IOException("Invalid URI format: " + idpMetadataUri, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Resource getSpCert() {
|
public Resource getKeystoreResource() {
|
||||||
if (spCert.startsWith("classpath:")) {
|
if (keystoreLocation.startsWith("classpath:")) {
|
||||||
return new ClassPathResource(spCert.substring("classpath:".length()));
|
return new ClassPathResource(
|
||||||
} else {
|
keystoreLocation.substring("classpath:".length()));
|
||||||
return new FileSystemResource(spCert);
|
} else {
|
||||||
}
|
return new FileSystemResource(keystoreLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Resource getidpCert() {
|
|
||||||
if (idpCert.startsWith("classpath:")) {
|
|
||||||
return new ClassPathResource(idpCert.substring("classpath:".length()));
|
|
||||||
} else {
|
|
||||||
return new FileSystemResource(idpCert);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Resource getPrivateKey() {
|
|
||||||
if (privateKey.startsWith("classpath:")) {
|
|
||||||
return new ClassPathResource(privateKey.substring("classpath:".length()));
|
|
||||||
} else {
|
|
||||||
return new FileSystemResource(privateKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ public class Provider implements ProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isValid(Collection<String> value, String name) {
|
protected boolean isValid(Collection<String> value, String name) {
|
||||||
@@ -26,55 +27,66 @@ public class Provider implements ProviderInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
// throw new IllegalArgumentException(getName() + ": " + name + " is required!");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<String> getScopes() {
|
public Collection<String> getScopes() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getScope'");
|
throw new UnsupportedOperationException("Unimplemented method 'getScope'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setScopes(String scopes) {
|
public void setScopes(String scopes) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setScope'");
|
throw new UnsupportedOperationException("Unimplemented method 'setScope'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getUseAsUsername() {
|
public String getUseAsUsername() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'");
|
throw new UnsupportedOperationException("Unimplemented method 'getUseAsUsername'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUseAsUsername(String useAsUsername) {
|
public void setUseAsUsername(String useAsUsername) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'");
|
throw new UnsupportedOperationException("Unimplemented method 'setUseAsUsername'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIssuer() {
|
public String getIssuer() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getIssuer'");
|
throw new UnsupportedOperationException("Unimplemented method 'getIssuer'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setIssuer(String issuer) {
|
public void setIssuer(String issuer) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setIssuer'");
|
throw new UnsupportedOperationException("Unimplemented method 'setIssuer'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientSecret() {
|
public String getClientSecret() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'");
|
throw new UnsupportedOperationException("Unimplemented method 'getClientSecret'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setClientSecret(String clientSecret) {
|
public void setClientSecret(String clientSecret) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'");
|
throw new UnsupportedOperationException("Unimplemented method 'setClientSecret'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getClientId() {
|
public String getClientId() {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'getClientId'");
|
throw new UnsupportedOperationException("Unimplemented method 'getClientId'");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setClientId(String clientId) {
|
public void setClientId(String clientId) {
|
||||||
|
// TODO Auto-generated method stub
|
||||||
throw new UnsupportedOperationException("Unimplemented method 'setClientId'");
|
throw new UnsupportedOperationException("Unimplemented method 'setClientId'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
import com.posthog.java.PostHog;
|
import com.posthog.java.PostHog;
|
||||||
|
|
||||||
import stirling.software.SPDF.controller.api.pipeline.UserServiceInterface;
|
|
||||||
import stirling.software.SPDF.model.ApplicationProperties;
|
import stirling.software.SPDF.model.ApplicationProperties;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -27,18 +26,15 @@ public class PostHogService {
|
|||||||
private final PostHog postHog;
|
private final PostHog postHog;
|
||||||
private final String uniqueId;
|
private final String uniqueId;
|
||||||
private final ApplicationProperties applicationProperties;
|
private final ApplicationProperties applicationProperties;
|
||||||
private final UserServiceInterface userService;
|
|
||||||
|
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
public PostHogService(
|
public PostHogService(
|
||||||
PostHog postHog,
|
PostHog postHog,
|
||||||
@Qualifier("UUID") String uuid,
|
@Qualifier("UUID") String uuid,
|
||||||
ApplicationProperties applicationProperties, @Autowired(required = false) UserServiceInterface userService) {
|
ApplicationProperties applicationProperties) {
|
||||||
this.postHog = postHog;
|
this.postHog = postHog;
|
||||||
this.uniqueId = uuid;
|
this.uniqueId = uuid;
|
||||||
this.applicationProperties = applicationProperties;
|
this.applicationProperties = applicationProperties;
|
||||||
this.userService = userService;
|
|
||||||
captureSystemInfo();
|
captureSystemInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,11 +133,6 @@ public class PostHogService {
|
|||||||
metrics.put("docker_metrics", getDockerMetrics());
|
metrics.put("docker_metrics", getDockerMetrics());
|
||||||
}
|
}
|
||||||
metrics.put("application_properties", captureApplicationProperties());
|
metrics.put("application_properties", captureApplicationProperties());
|
||||||
|
|
||||||
|
|
||||||
if(userService != null) {
|
|
||||||
metrics.put("total_users_created", userService.getTotalUsersCount());
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
metrics.put("error", e.getMessage());
|
metrics.put("error", e.getMessage());
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=تم تعطيل المستخدم، تم حظر تسجيل
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=حجب تلقائي
|
autoRedact.title=حجب تلقائي
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Размер на шрифт
|
addPageNumbers.fontSize=Font Size
|
||||||
addPageNumbers.fontName=Име на шрифт
|
addPageNumbers.fontName=Font Name
|
||||||
pdfPrompt=Изберете PDF(и)
|
pdfPrompt=Изберете PDF(и)
|
||||||
multiPdfPrompt=Изберете PDF (2+)
|
multiPdfPrompt=Изберете PDF (2+)
|
||||||
multiPdfDropPrompt=Изберете (или плъзнете и пуснете) всички PDF файлове, от които се нуждаете
|
multiPdfDropPrompt=Изберете (или плъзнете и пуснете) всички PDF файлове, от които се нуждаете
|
||||||
@@ -56,12 +56,12 @@ userNotFoundMessage=Потребителят не е намерен
|
|||||||
incorrectPasswordMessage=Текущата парола е неправилна.
|
incorrectPasswordMessage=Текущата парола е неправилна.
|
||||||
usernameExistsMessage=Новият потребител вече съществува.
|
usernameExistsMessage=Новият потребител вече съществува.
|
||||||
invalidUsernameMessage=Невалидно потребителско име, потребителското име може да съдържа само букви, цифри и следните специални знаци @._+- или трябва да е валиден имейл адрес.
|
invalidUsernameMessage=Невалидно потребителско име, потребителското име може да съдържа само букви, цифри и следните специални знаци @._+- или трябва да е валиден имейл адрес.
|
||||||
invalidPasswordMessage=Паролата не трябва да е празна и не трябва да има интервали в началото или в края.
|
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
||||||
confirmPasswordErrorMessage=Нова парола и Потвърждаване на новата парола трябва да съвпадат.
|
confirmPasswordErrorMessage=New Password and Confirm New Password must match.
|
||||||
deleteCurrentUserMessage=Не може да се изтрие вписания в момента потребител.
|
deleteCurrentUserMessage=Не може да се изтрие вписания в момента потребител.
|
||||||
deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
|
deleteUsernameExistsMessage=Потребителското име не съществува и не може да бъде изтрито.
|
||||||
downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
|
downgradeCurrentUserMessage=Не може да се понижи ролята на текущия потребител
|
||||||
disabledCurrentUserMessage=Текущият потребител не може да бъде деактивиран
|
disabledCurrentUserMessage=The current user cannot be disabled
|
||||||
downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
|
downgradeCurrentUserLongMessage=Не може да се понижи ролята на текущия потребител. Следователно текущият потребител няма да бъде показан.
|
||||||
userAlreadyExistsOAuthMessage=Потребителят вече съществува като OAuth2 потребител.
|
userAlreadyExistsOAuthMessage=Потребителят вече съществува като OAuth2 потребител.
|
||||||
userAlreadyExistsWebMessage=Потребителят вече съществува като уеб-потребител.
|
userAlreadyExistsWebMessage=Потребителят вече съществува като уеб-потребител.
|
||||||
@@ -75,16 +75,16 @@ visitGithub=Посетете Github Repository
|
|||||||
donate=Направете дарение
|
donate=Направете дарение
|
||||||
color=Цвят
|
color=Цвят
|
||||||
sponsor=Спонсор
|
sponsor=Спонсор
|
||||||
info=Информация
|
info=Info
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Страница
|
page=Page
|
||||||
pages=Страници
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Политика за поверителност
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Правила и условия
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=Достъпност
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=Политика за бисквитки
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=Отпечатък
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -96,7 +96,7 @@ pipeline.defaultOption=Персонализиран
|
|||||||
pipeline.submitButton=Подайте
|
pipeline.submitButton=Подайте
|
||||||
pipeline.help=Pipeline Помощ
|
pipeline.help=Pipeline Помощ
|
||||||
pipeline.scanHelp=Помощ за сканиране на папки
|
pipeline.scanHelp=Помощ за сканиране на папки
|
||||||
pipeline.deletePrompt=Сигурни ли сте, че искате да изтриете pipeline
|
pipeline.deletePrompt=Are you sure you want to delete pipeline
|
||||||
|
|
||||||
######################
|
######################
|
||||||
# Pipeline Options #
|
# Pipeline Options #
|
||||||
@@ -114,21 +114,21 @@ pipelineOptions.validateButton=Валидирай
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Направете надстройка до Pro версията
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Тази функция е достъпна само за потребители на Pro версията.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro поддържа YAML конфигурационни файлове и други SSO функции.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Търсите повече функции за управление на потребителите? Погледнете за Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Искате ли да подобрите Stirling PDF?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF включва анализи, за да ни помогне да подобрим продукта. Ние не проследяваме лична информация или съдържание на файлове.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Моля, обмислете възможността за анализ, за да помогнете на Stirling-PDF да расте и да ни позволи да разберем по-добре нашите потребители.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Активиране на анализа
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Деактивиране на анализа
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Можете да промените настройките за анализ във config/settings.yml файла
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -145,7 +145,7 @@ navbar.sections.convertFrom=Преобразуване от PDF
|
|||||||
navbar.sections.security=Подписване и сигурност
|
navbar.sections.security=Подписване и сигурност
|
||||||
navbar.sections.advance=Разширено
|
navbar.sections.advance=Разширено
|
||||||
navbar.sections.edit=Преглед и редактиране
|
navbar.sections.edit=Преглед и редактиране
|
||||||
navbar.sections.popular=Популярни
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -202,9 +202,9 @@ adminUserSettings.header=Настройки за администраторск
|
|||||||
adminUserSettings.admin=Администратор
|
adminUserSettings.admin=Администратор
|
||||||
adminUserSettings.user=Потребител
|
adminUserSettings.user=Потребител
|
||||||
adminUserSettings.addUser=Добавяне на нов потребител
|
adminUserSettings.addUser=Добавяне на нов потребител
|
||||||
adminUserSettings.deleteUser=Изтриване на потребител
|
adminUserSettings.deleteUser=Delete User
|
||||||
adminUserSettings.confirmDeleteUser=Трябва ли потребителят да бъде изтрит?
|
adminUserSettings.confirmDeleteUser=Should the user be deleted?
|
||||||
adminUserSettings.confirmChangeUserStatus=Трябва ли потребителят да бъде деактивиран/активиран?
|
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||||
adminUserSettings.usernameInfo=Потребителското име може да съдържа само букви, цифри и следните специални символи @._+- или трябва да е валиден имейл адрес.
|
adminUserSettings.usernameInfo=Потребителското име може да съдържа само букви, цифри и следните специални символи @._+- или трябва да е валиден имейл адрес.
|
||||||
adminUserSettings.roles=Роли
|
adminUserSettings.roles=Роли
|
||||||
adminUserSettings.role=Роля
|
adminUserSettings.role=Роля
|
||||||
@@ -218,32 +218,32 @@ adminUserSettings.forceChange=Принудете потребителя да п
|
|||||||
adminUserSettings.submit=Съхранете потребителя
|
adminUserSettings.submit=Съхранете потребителя
|
||||||
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
|
adminUserSettings.changeUserRole=Промяна на ролята на потребителя
|
||||||
adminUserSettings.authenticated=Удостоверен
|
adminUserSettings.authenticated=Удостоверен
|
||||||
adminUserSettings.editOwnProfil=Редактиране на собствен профил
|
adminUserSettings.editOwnProfil=Edit own profile
|
||||||
adminUserSettings.enabledUser=активиран потребител
|
adminUserSettings.enabledUser=enabled user
|
||||||
adminUserSettings.disabledUser=деактивиран потребител
|
adminUserSettings.disabledUser=disabled user
|
||||||
adminUserSettings.activeUsers=Активни потребители:
|
adminUserSettings.activeUsers=Active Users:
|
||||||
adminUserSettings.disabledUsers=Деактивирани потребители:
|
adminUserSettings.disabledUsers=Disabled Users:
|
||||||
adminUserSettings.totalUsers=Общо потребители:
|
adminUserSettings.totalUsers=Total Users:
|
||||||
adminUserSettings.lastRequest=Последна заявка
|
adminUserSettings.lastRequest=Last Request
|
||||||
|
|
||||||
|
|
||||||
database.title=Импорт/Експорт на база данни
|
database.title=Database Import/Export
|
||||||
database.header=Импорт/Експорт на база данни
|
database.header=Database Import/Export
|
||||||
database.fileName=Име на файл
|
database.fileName=File Name
|
||||||
database.creationDate=Дата на създаване
|
database.creationDate=Creation Date
|
||||||
database.fileSize=Размер на файла
|
database.fileSize=File Size
|
||||||
database.deleteBackupFile=Изтриване на архивен файл
|
database.deleteBackupFile=Delete Backup File
|
||||||
database.importBackupFile=Импортиране на архивен файл
|
database.importBackupFile=Import Backup File
|
||||||
database.downloadBackupFile=Изтеглете архивен файл
|
database.downloadBackupFile=Download Backup File
|
||||||
database.info_1=Когато импортирате данни, е от решаващо значение да осигурите правилната структура. Ако не сте сигурни в това, което правите, потърсете съвет и подкрепа от професионалист. Грешка в структурата може да причини неизправност на приложението, включително пълна невъзможност за стартиране на приложението.
|
database.info_1=When importing data, it is crucial to ensure the correct structure. If you are unsure of what you are doing, seek advice and support from a professional. An error in the structure can cause application malfunctions, up to and including the complete inability to run the application.
|
||||||
database.info_2=Името на файла няма значение при качване. След това ще бъде преименуван, за да следва формата backup_user_yyyyMMddHHmm.sql, осигурявайки последователна конвенция за именуване.
|
database.info_2=The file name does not matter when uploading. It will be renamed afterward to follow the format backup_user_yyyyMMddHHmm.sql, ensuring a consistent naming convention.
|
||||||
database.submit=Импортиране на резервно копие
|
database.submit=Import Backup
|
||||||
database.importIntoDatabaseSuccessed=Импортирането в базата данни бе успешно
|
database.importIntoDatabaseSuccessed=Import into database successed
|
||||||
database.fileNotFound=Файлът не е намерен
|
database.fileNotFound=File not Found
|
||||||
database.fileNullOrEmpty=Файлът не трябва да е нулев или празен
|
database.fileNullOrEmpty=File must not be null or empty
|
||||||
database.failedImportFile=Неуспешно импортиране на файл
|
database.failedImportFile=Failed Import File
|
||||||
|
|
||||||
session.expired=Вашата сесия е изтекла. Моля, опреснете страницата и опитайте отново.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -390,9 +390,9 @@ home.certSign.title=Подпишете със сертификат
|
|||||||
home.certSign.desc=Подписва PDF със сертификат/ключ (PEM/P12)
|
home.certSign.desc=Подписва PDF със сертификат/ключ (PEM/P12)
|
||||||
certSign.tags=удостоверяване,PEM,P12,официален,шифроване
|
certSign.tags=удостоверяване,PEM,P12,официален,шифроване
|
||||||
|
|
||||||
home.removeCertSign.title=Премахване на знака за сертификат
|
home.removeCertSign.title=Remove Certificate Sign
|
||||||
home.removeCertSign.desc=Премахване на подпис на сертификат от PDF
|
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||||
removeCertSign.tags=удостоверяване,PEM,P12,официален,декриптиране
|
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=Оформление с няколко страници
|
home.pageLayout.title=Оформление с няколко страници
|
||||||
home.pageLayout.desc=Слейте няколко страници от PDF документ в една страница
|
home.pageLayout.desc=Слейте няколко страници от PDF документ в една страница
|
||||||
@@ -498,33 +498,33 @@ home.BookToPDF.title=Книга към PDF
|
|||||||
home.BookToPDF.desc=Преобразува формати на книги/комикси в PDF с помощта на calibre
|
home.BookToPDF.desc=Преобразува формати на книги/комикси в PDF с помощта на calibre
|
||||||
BookToPDF.tags=Книга,комикс,calibre,конвертиране,манга,Amazon,Kindle
|
BookToPDF.tags=Книга,комикс,calibre,конвертиране,манга,Amazon,Kindle
|
||||||
|
|
||||||
home.removeImagePdf.title=Премахване на изображение
|
home.removeImagePdf.title=Remove image
|
||||||
home.removeImagePdf.desc=Премахнете изображението от PDF, за да намалите размера на файла
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Премахване на изображение, операции на страници, админ страна, страна на сървъра
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Разделете PDF по глави
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Разделете PDF на множество файлове въз основа на неговата структура на глави.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=разделяне, глави, отметки, организиране
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Замени-инвертиране-на-цвят
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Замяна-инвертиране на цвят PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Замяна и обръщане на цвят
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Заменете цвета на текста и фона в PDF и обърнете пълния цвят на PDF, за да намалите размера на файла
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Замяна на цвят, операции на страници, заден край, страна на сървъра
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Опции за замяна или инвертиране на цвят
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=По подразбиране (цветове с висок контраст по подразбиране)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=По избор (персонализирани цветове)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Пълно инвертиране (Инвертиране на всички цветове)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Цветови опции с висок контраст
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=Бял текст на черен фон
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Черен текст на бял фон
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Жълт текст на черен фон
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Зелен текст на черен фон
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Изберете цвят на текста
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Изберете цвят на фона
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Замени
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -543,17 +543,18 @@ login.locked=Вашият акаунт е заключен.
|
|||||||
login.signinTitle=Моля впишете се
|
login.signinTitle=Моля впишете се
|
||||||
login.ssoSignIn=Влизане чрез еднократно влизане
|
login.ssoSignIn=Влизане чрез еднократно влизане
|
||||||
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
login.oauth2AutoCreateDisabled=OAUTH2 Автоматично създаване на потребител е деактивирано
|
||||||
login.oauth2AdminBlockedUser=Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
login.oauth2RequestNotFound=Заявката за оторизация не е намерена
|
||||||
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
login.oauth2InvalidUserInfoResponse=Невалидна информация за потребителя
|
||||||
login.oauth2invalidRequest=Невалидна заявка
|
login.oauth2invalidRequest=Невалидна заявка
|
||||||
login.oauth2AccessDenied=Отказан достъп
|
login.oauth2AccessDenied=Отказан достъп
|
||||||
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
|
login.oauth2InvalidTokenResponse=Невалиден отговор на токена
|
||||||
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
|
login.oauth2InvalidIdToken=Невалиден токен за идентификатор
|
||||||
login.userIsDisabled=Потребителят е деактивиран, влизането в момента е блокирано с това потребителско име. Моля, свържете се с администратора.
|
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||||
login.alreadyLoggedIn=Вече сте влезли в
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=устройства. Моля, излезте от устройствата и опитайте отново.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Имате твърде много активни сесии
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Автоматично редактиране
|
autoRedact.title=Автоматично редактиране
|
||||||
@@ -728,7 +729,7 @@ pageLayout.submit=Подайте
|
|||||||
scalePages.title=Коригиране на мащаба на страницата
|
scalePages.title=Коригиране на мащаба на страницата
|
||||||
scalePages.header=Коригиране на мащаба на страницата
|
scalePages.header=Коригиране на мащаба на страницата
|
||||||
scalePages.pageSize=Размер на страница от документа.
|
scalePages.pageSize=Размер на страница от документа.
|
||||||
scalePages.keepPageSize=Оригинален размер
|
scalePages.keepPageSize=Original Size
|
||||||
scalePages.scaleFactor=Ниво на мащабиране (изрязване) на страница.
|
scalePages.scaleFactor=Ниво на мащабиране (изрязване) на страница.
|
||||||
scalePages.submit=Подайте
|
scalePages.submit=Подайте
|
||||||
|
|
||||||
@@ -752,10 +753,10 @@ certSign.submit=Подпишете PDF
|
|||||||
|
|
||||||
|
|
||||||
#removeCertSign
|
#removeCertSign
|
||||||
removeCertSign.title=Премахване на подписа на сертификата
|
removeCertSign.title=Remove Certificate Signature
|
||||||
removeCertSign.header=Премахнете цифровия сертификат от PDF
|
removeCertSign.header=Remove the digital certificate from the PDF
|
||||||
removeCertSign.selectPDF=Изберете PDF файл:
|
removeCertSign.selectPDF=Select a PDF file:
|
||||||
removeCertSign.submit=Премахване на подпис
|
removeCertSign.submit=Remove Signature
|
||||||
|
|
||||||
|
|
||||||
#removeBlanks
|
#removeBlanks
|
||||||
@@ -777,8 +778,8 @@ removeAnnotations.submit=Премахване
|
|||||||
#compare
|
#compare
|
||||||
compare.title=Сравнявай
|
compare.title=Сравнявай
|
||||||
compare.header=Сравнявай PDF-и
|
compare.header=Сравнявай PDF-и
|
||||||
compare.highlightColor.1=Цвят на маркирането 1:
|
compare.highlightColor.1=Highlight Color 1:
|
||||||
compare.highlightColor.2=Цвят на маркирането 2:
|
compare.highlightColor.2=Highlight Color 2:
|
||||||
compare.document.1=Документ 1
|
compare.document.1=Документ 1
|
||||||
compare.document.2=Документ 2
|
compare.document.2=Документ 2
|
||||||
compare.submit=Сравнявай
|
compare.submit=Сравнявай
|
||||||
@@ -830,7 +831,7 @@ ScannerImageSplit.selectText.7=Минимална контурна площ:
|
|||||||
ScannerImageSplit.selectText.8=Задава минималния праг на контурната площ за изображение
|
ScannerImageSplit.selectText.8=Задава минималния праг на контурната площ за изображение
|
||||||
ScannerImageSplit.selectText.9=Размер на рамката:
|
ScannerImageSplit.selectText.9=Размер на рамката:
|
||||||
ScannerImageSplit.selectText.10=Задава размера на добавената и премахната граница, за да предотврати бели граници към изхода (по подразбиране: 1).
|
ScannerImageSplit.selectText.10=Задава размера на добавената и премахната граница, за да предотврати бели граници към изхода (по подразбиране: 1).
|
||||||
ScannerImageSplit.info=Python не е инсталиран. Изисква се да се изпълнява.
|
ScannerImageSplit.info=Python is not installed. It is required to run.
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
@@ -857,7 +858,7 @@ ocr.submit=Обработка на PDF чрез OCR
|
|||||||
extractImages.title=Извличане на изображения
|
extractImages.title=Извличане на изображения
|
||||||
extractImages.header=Извличане на изображения
|
extractImages.header=Извличане на изображения
|
||||||
extractImages.selectText=Изберете формат на изображението, в който да преобразувате извлечените изображения
|
extractImages.selectText=Изберете формат на изображението, в който да преобразувате извлечените изображения
|
||||||
extractImages.allowDuplicates=Запазване на дублирани изображения
|
extractImages.allowDuplicates=Save duplicate images
|
||||||
extractImages.submit=Извличане
|
extractImages.submit=Извличане
|
||||||
|
|
||||||
|
|
||||||
@@ -895,7 +896,7 @@ merge.title=Обединяване
|
|||||||
merge.header=Обединяване на множество PDF файлове (2+)
|
merge.header=Обединяване на множество PDF файлове (2+)
|
||||||
merge.sortByName=Сортиране по име
|
merge.sortByName=Сортиране по име
|
||||||
merge.sortByDate=Сортиране по дата
|
merge.sortByDate=Сортиране по дата
|
||||||
merge.removeCertSign=Премахване на цифровия подпис в обединения файл?
|
merge.removeCertSign=Remove digital signature in the merged file?
|
||||||
merge.submit=Обединяване
|
merge.submit=Обединяване
|
||||||
|
|
||||||
|
|
||||||
@@ -913,7 +914,7 @@ pdfOrganiser.mode.6=Четно-нечетно разделяне
|
|||||||
pdfOrganiser.mode.7=Премахни първо
|
pdfOrganiser.mode.7=Премахни първо
|
||||||
pdfOrganiser.mode.8=Премахване на последния
|
pdfOrganiser.mode.8=Премахване на последния
|
||||||
pdfOrganiser.mode.9=Премахване на първия и последния
|
pdfOrganiser.mode.9=Премахване на първия и последния
|
||||||
pdfOrganiser.mode.10=Обединяване на четно и нечетно
|
pdfOrganiser.mode.10=Odd-Even Merge
|
||||||
pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
pdfOrganiser.placeholder=(напр. 1,3,2 или 4-8,2,10-12 или 2n-1)
|
||||||
|
|
||||||
|
|
||||||
@@ -982,7 +983,7 @@ pdfToImage.color=Цвят
|
|||||||
pdfToImage.grey=Скала на сивото
|
pdfToImage.grey=Скала на сивото
|
||||||
pdfToImage.blackwhite=Черно и бяло (може да загубите данни!)
|
pdfToImage.blackwhite=Черно и бяло (може да загубите данни!)
|
||||||
pdfToImage.submit=Преобразуване
|
pdfToImage.submit=Преобразуване
|
||||||
pdfToImage.info=Python не е инсталиран. Изисква се за конвертиране на WebP.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -1019,7 +1020,7 @@ watermark.selectText.6=дължинаSpacer (Разстояние между в
|
|||||||
watermark.selectText.7=Непрозрачност (0% - 100%):
|
watermark.selectText.7=Непрозрачност (0% - 100%):
|
||||||
watermark.selectText.8=Тип воден знак:
|
watermark.selectText.8=Тип воден знак:
|
||||||
watermark.selectText.9=Изображение за воден знак:
|
watermark.selectText.9=Изображение за воден знак:
|
||||||
watermark.selectText.10=Конвертирайте PDF в PDF-изображение
|
watermark.selectText.10=Convert PDF to PDF-Image
|
||||||
watermark.submit=Добавяне на воден знак
|
watermark.submit=Добавяне на воден знак
|
||||||
watermark.type.1=Текст
|
watermark.type.1=Текст
|
||||||
watermark.type.2=Изображение
|
watermark.type.2=Изображение
|
||||||
@@ -1076,7 +1077,7 @@ pdfToPDFA.credit=Тази услуга използва ghostscript за PDF/A
|
|||||||
pdfToPDFA.submit=Преобразуване
|
pdfToPDFA.submit=Преобразуване
|
||||||
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
pdfToPDFA.tip=В момента не работи за няколко входа наведнъж
|
||||||
pdfToPDFA.outputFormat=Изходен формат
|
pdfToPDFA.outputFormat=Изходен формат
|
||||||
pdfToPDFA.pdfWithDigitalSignature=PDF файлът съдържа цифров подпис. Това ще бъде премахнато в следващата стъпка.
|
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||||
|
|
||||||
|
|
||||||
#PDFToWord
|
#PDFToWord
|
||||||
@@ -1117,10 +1118,10 @@ PDFToXML.credit=Тази услуга използва LibreOffice за прео
|
|||||||
PDFToXML.submit=Преобразуване
|
PDFToXML.submit=Преобразуване
|
||||||
|
|
||||||
#PDFToCSV
|
#PDFToCSV
|
||||||
PDFToCSV.title=PDF към CSV
|
PDFToCSV.title=PDF ??? CSV
|
||||||
PDFToCSV.header=PDF към CSV
|
PDFToCSV.header=PDF ??? CSV
|
||||||
PDFToCSV.prompt=Изберете страница за извличане на таблица
|
PDFToCSV.prompt=Изберете страница за извличане на таблица
|
||||||
PDFToCSV.submit=Преобразуване
|
PDFToCSV.submit=????
|
||||||
|
|
||||||
#split-by-size-or-count
|
#split-by-size-or-count
|
||||||
split-by-size-or-count.title=Разделяне на PDF по размер или брой
|
split-by-size-or-count.title=Разделяне на PDF по размер или брой
|
||||||
@@ -1178,15 +1179,15 @@ licenses.version=Версия
|
|||||||
licenses.license=Лиценз
|
licenses.license=Лиценз
|
||||||
|
|
||||||
#survey
|
#survey
|
||||||
survey.nav=Анкета
|
survey.nav=Survey
|
||||||
survey.title=Stirling-PDF Анкета
|
survey.title=Stirling-PDF Survey
|
||||||
survey.description=Stirling-PDF няма проследяване, така че искаме да чуем мнението на нашите потребители за подобряване на Stirling-PDF!
|
survey.description=Stirling-PDF has no tracking so we want to hear from our users to improve Stirling-PDF!
|
||||||
survey.changes=Stirling-PDF се промени от последното проучване! За да научите повече, моля, проверете публикацията в нашия блог тук:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=С тези промени получаваме платена бизнес подкрепа и финансиране
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Моля, помислете дали да не участвате в нашата анкета!
|
survey.please=Please consider taking our survey!
|
||||||
survey.disabled=(Изскачащият прозорец с анкетата ще бъде деактивиран при следващите актуализации, но ще бъде наличен в долната част на страницата)
|
survey.disabled=(Survey popup will be disabled in following updates but available at foot of page)
|
||||||
survey.button=Участвайте в анкетата
|
survey.button=Take Survey
|
||||||
survey.dontShowAgain=Не показвай повече
|
survey.dontShowAgain=Don't show again
|
||||||
|
|
||||||
|
|
||||||
#error
|
#error
|
||||||
@@ -1204,21 +1205,21 @@ error.discordSubmit=Discord - Изпратете запитване за под
|
|||||||
|
|
||||||
|
|
||||||
#remove-image
|
#remove-image
|
||||||
removeImage.title=Премахване на изображението
|
removeImage.title=Remove image
|
||||||
removeImage.header=Премахване на изображението
|
removeImage.header=Remove image
|
||||||
removeImage.removeImage=Премахване на изображението
|
removeImage.removeImage=Remove image
|
||||||
removeImage.submit=Премахване на изображението
|
removeImage.submit=Remove image
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Разделете PDF по глави
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=Разделете PDF по глави
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=Ниво на отметка
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=Включете метаданни
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=Разрешаване на дубликати
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=Този инструмент разделя PDF файл на множество PDF файлове въз основа на неговата структура на глави.
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=Ниво на отметка: Изберете нивото на отметките, които да използвате за разделяне (0 за най-високо ниво, 1 за второ ниво и т.н.).
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=Включване на метаданни: Ако е отметнато, метаданните на оригиналния PDF ще бъдат включени във всеки разделен PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Разрешаване на дубликати: Ако е отметнато, позволява множество отметки на една и съща страница за създаване на отделни PDF файлове.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Разделяне на PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redact
|
autoRedact.title=Auto Redact
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redact
|
autoRedact.title=Auto Redact
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=Bruger er deaktiveret, login er i øjeblikket blokeret med
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Rediger
|
autoRedact.title=Auto Rediger
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=Benutzer ist deaktiviert, die Anmeldung ist mit diesem Benu
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Automatisch zensieren/schwärzen
|
autoRedact.title=Automatisch zensieren/schwärzen
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Αυτόματο Μαύρισμα Κειμένου
|
autoRedact.title=Αυτόματο Μαύρισμα Κειμένου
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redact
|
autoRedact.title=Auto Redact
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redact
|
autoRedact.title=Auto Redact
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=El usuario está desactivado, actualmente el acceso está b
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redactar
|
autoRedact.title=Auto Redactar
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Idatzi
|
autoRedact.title=Auto Idatzi
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ navbar.sections.convertFrom=Convertir depuis PDF
|
|||||||
navbar.sections.security=Signature et sécurité
|
navbar.sections.security=Signature et sécurité
|
||||||
navbar.sections.advance=Mode avancé
|
navbar.sections.advance=Mode avancé
|
||||||
navbar.sections.edit=Voir et modifier
|
navbar.sections.edit=Voir et modifier
|
||||||
navbar.sections.popular=Populaire
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -202,9 +202,9 @@ adminUserSettings.header=Administration des paramètres des utilisateurs
|
|||||||
adminUserSettings.admin=Administateur
|
adminUserSettings.admin=Administateur
|
||||||
adminUserSettings.user=Utilisateur
|
adminUserSettings.user=Utilisateur
|
||||||
adminUserSettings.addUser=Ajouter un utilisateur
|
adminUserSettings.addUser=Ajouter un utilisateur
|
||||||
adminUserSettings.deleteUser=Supprimer l'utilisateur
|
adminUserSettings.deleteUser=Delete User
|
||||||
adminUserSettings.confirmDeleteUser=Voulez vous vraiment supprimer l'utilisateur ?
|
adminUserSettings.confirmDeleteUser=Should the user be deleted?
|
||||||
adminUserSettings.confirmChangeUserStatus=Voulez vous vraiment déactiver/réactiver l'utilisateur ?
|
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||||
adminUserSettings.usernameInfo=Le nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide.
|
adminUserSettings.usernameInfo=Le nom d'utilisateur ne peut contenir que des lettres, des chiffres et les caractères spéciaux suivants @._+- ou doit être une adresse e-mail valide.
|
||||||
adminUserSettings.roles=Rôles
|
adminUserSettings.roles=Rôles
|
||||||
adminUserSettings.role=Rôle
|
adminUserSettings.role=Rôle
|
||||||
@@ -218,13 +218,13 @@ adminUserSettings.forceChange=Forcer l’utilisateur à changer son nom d’util
|
|||||||
adminUserSettings.submit=Ajouter
|
adminUserSettings.submit=Ajouter
|
||||||
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
|
adminUserSettings.changeUserRole=Changer le rôle de l'utilisateur
|
||||||
adminUserSettings.authenticated=Authentifié
|
adminUserSettings.authenticated=Authentifié
|
||||||
adminUserSettings.editOwnProfil=Éditer son propre profil
|
adminUserSettings.editOwnProfil=Edit own profile
|
||||||
adminUserSettings.enabledUser=Utilisateur activé
|
adminUserSettings.enabledUser=enabled user
|
||||||
adminUserSettings.disabledUser=Utilisateur désactivé
|
adminUserSettings.disabledUser=disabled user
|
||||||
adminUserSettings.activeUsers=Utilisateurs actifs :
|
adminUserSettings.activeUsers=Active Users:
|
||||||
adminUserSettings.disabledUsers=Utilisateurs désactivés :
|
adminUserSettings.disabledUsers=Disabled Users:
|
||||||
adminUserSettings.totalUsers=Utilisateurs au total :
|
adminUserSettings.totalUsers=Total Users:
|
||||||
adminUserSettings.lastRequest=Dernière requête
|
adminUserSettings.lastRequest=Last Request
|
||||||
|
|
||||||
|
|
||||||
database.title=Database Import/Export
|
database.title=Database Import/Export
|
||||||
@@ -243,7 +243,7 @@ database.fileNotFound=File not Found
|
|||||||
database.fileNullOrEmpty=File must not be null or empty
|
database.fileNullOrEmpty=File must not be null or empty
|
||||||
database.failedImportFile=Failed Import File
|
database.failedImportFile=Failed Import File
|
||||||
|
|
||||||
session.expired=Votre session a expiré. Veuillez recharger la page et réessayer.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -321,7 +321,7 @@ home.changeMetadata.desc=Modifiez, supprimez ou ajoutez des métadonnées à un
|
|||||||
changeMetadata.tags=métadonnées,titre,auteur,date,création,heure,éditeur,statistiques,title,author,date,creation,time,publisher,producer,stats,metadata
|
changeMetadata.tags=métadonnées,titre,auteur,date,création,heure,éditeur,statistiques,title,author,date,creation,time,publisher,producer,stats,metadata
|
||||||
|
|
||||||
home.fileToPDF.title=Fichier en PDF
|
home.fileToPDF.title=Fichier en PDF
|
||||||
home.fileToPDF.desc=Convertissez presque n’importe quel fichier en PDF (DOCX, PNG, XLS, PPT, TXT, etc.).
|
home.fileToPDF.desc=Convertissez presque n’importe quel fichiers en PDF (DOCX, PNG, XLS, PPT, TXT et plus).
|
||||||
fileToPDF.tags=convertion,transformation,format,document,image,slide,texte,conversion,office,docs,word,excel,powerpoint
|
fileToPDF.tags=convertion,transformation,format,document,image,slide,texte,conversion,office,docs,word,excel,powerpoint
|
||||||
|
|
||||||
home.ocr.title=OCR / Nettoyage des numérisations
|
home.ocr.title=OCR / Nettoyage des numérisations
|
||||||
@@ -390,9 +390,9 @@ home.certSign.title=Signer avec un certificat
|
|||||||
home.certSign.desc=Signez un PDF avec un certificat ou une clé (PEM/P12).
|
home.certSign.desc=Signez un PDF avec un certificat ou une clé (PEM/P12).
|
||||||
certSign.tags=signer,chiffrer,certificat,authenticate,PEM,P12,official,encrypt
|
certSign.tags=signer,chiffrer,certificat,authenticate,PEM,P12,official,encrypt
|
||||||
|
|
||||||
home.removeCertSign.title=Supprimer la signature par certificat
|
home.removeCertSign.title=Remove Certificate Sign
|
||||||
home.removeCertSign.desc=Supprimez la signature par certificat d'un PDF
|
home.removeCertSign.desc=Remove certificate signature from PDF
|
||||||
removeCertSign.tags=signer,chiffrer,certificat,authenticate,PEM,P12,official,decrypt
|
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=Fusionner des pages
|
home.pageLayout.title=Fusionner des pages
|
||||||
home.pageLayout.desc=Fusionnez plusieurs pages d’un PDF en une seule.
|
home.pageLayout.desc=Fusionnez plusieurs pages d’un PDF en une seule.
|
||||||
@@ -498,14 +498,14 @@ home.BookToPDF.title=eBook vers PDF
|
|||||||
home.BookToPDF.desc=Convertit les formats de livres/bandes dessinées en PDF à l'aide de calibre
|
home.BookToPDF.desc=Convertit les formats de livres/bandes dessinées en PDF à l'aide de calibre
|
||||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||||
|
|
||||||
home.removeImagePdf.title=Supprimer les images
|
home.removeImagePdf.title=Remove image
|
||||||
home.removeImagePdf.desc=Supprimez les images d'un PDF pour réduire sa taille
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Images,Remove Image,Page operations,Back end,server side
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Séparer un PDF par chapitres
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Séparez un PDF en fichiers multiples en fonction de sa structure par chapitres.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=séparer,chapitres,split,chapters,bookmarks,organize
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Replace-Invert-Color
|
replace-color.title=Replace-Invert-Color
|
||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Caviarder automatiquement
|
autoRedact.title=Caviarder automatiquement
|
||||||
@@ -786,14 +787,14 @@ compare.submit=Comparer
|
|||||||
#BookToPDF
|
#BookToPDF
|
||||||
BookToPDF.title=Livres et BD vers PDF
|
BookToPDF.title=Livres et BD vers PDF
|
||||||
BookToPDF.header=Livre vers PDF
|
BookToPDF.header=Livre vers PDF
|
||||||
BookToPDF.credit=Utilise Calibre
|
BookToPDF.credit=Utiliser Calibre
|
||||||
BookToPDF.submit=Convertir
|
BookToPDF.submit=Convertir
|
||||||
|
|
||||||
#PDFToBook
|
#PDFToBook
|
||||||
PDFToBook.title=PDF vers Livre
|
PDFToBook.title=PDF vers Livre
|
||||||
PDFToBook.header=PDF vers Livre
|
PDFToBook.header=PDF vers Livre
|
||||||
PDFToBook.selectText.1=Format
|
PDFToBook.selectText.1=Format
|
||||||
PDFToBook.credit=Utilise Calibre
|
PDFToBook.credit=Utiliser Calibre
|
||||||
PDFToBook.submit=Convertir
|
PDFToBook.submit=Convertir
|
||||||
|
|
||||||
#sign
|
#sign
|
||||||
@@ -1076,7 +1077,7 @@ pdfToPDFA.credit=Ce service utilise ghostscript pour la conversion en PDF/A.
|
|||||||
pdfToPDFA.submit=Convertir
|
pdfToPDFA.submit=Convertir
|
||||||
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
|
pdfToPDFA.tip=Ne fonctionne actuellement pas pour plusieurs entrées à la fois
|
||||||
pdfToPDFA.outputFormat=Format de sortie
|
pdfToPDFA.outputFormat=Format de sortie
|
||||||
pdfToPDFA.pdfWithDigitalSignature=Le PDF contient une signature numérique. Elle sera supprimée dans l'étape suivante.
|
pdfToPDFA.pdfWithDigitalSignature=The PDF contains a digital signature. This will be removed in the next step.
|
||||||
|
|
||||||
|
|
||||||
#PDFToWord
|
#PDFToWord
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Redact
|
autoRedact.title=Auto Redact
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=स्वत: गोपनीयकरण
|
autoRedact.title=स्वत: गोपनीयकरण
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Automatsko uređivanje
|
autoRedact.title=Automatsko uređivanje
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Érzékeny tartalom eltávolítása
|
autoRedact.title=Érzékeny tartalom eltávolítása
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Redaksional Otomatis
|
autoRedact.title=Redaksional Otomatis
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ color=Colore
|
|||||||
sponsor=Sponsor
|
sponsor=Sponsor
|
||||||
info=Info
|
info=Info
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Pagina
|
page=Page
|
||||||
pages=Pagine
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Informativa sulla privacy
|
legal.privacy=Informativa sulla privacy
|
||||||
legal.terms=Termini e Condizioni
|
legal.terms=Termini e Condizioni
|
||||||
@@ -114,21 +114,21 @@ pipelineOptions.validateButton=Convalidare
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Aggiorna alla versione Pro
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Questa funzionalità è disponibile solo per gli utenti Pro.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro supporta i file di configurazione YAML e altre funzionalità SSO.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Cerchi altre funzionalità di gestione degli utenti? Dai un'occhiata a Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Vuoi migliorare Stirling PDF?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF ha opt-in analytics per aiutarci a migliorare il prodotto. Non tracciamo alcuna informazione personale o contenuto di file.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Si prega di prendere in considerazione l'attivazione dell'analytics per aiutare Stirling-PDF a crescere e consentirci di comprendere meglio i nostri utenti.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Abilita analytics
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Disabilita analytics
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=È possibile modificare le impostazioni per analitycs nel file config/settings.yml
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -145,7 +145,7 @@ navbar.sections.convertFrom=Converti da PDF
|
|||||||
navbar.sections.security=Firma & Sicurezza
|
navbar.sections.security=Firma & Sicurezza
|
||||||
navbar.sections.advance=Avanzate
|
navbar.sections.advance=Avanzate
|
||||||
navbar.sections.edit=Visualizza & Modifica
|
navbar.sections.edit=Visualizza & Modifica
|
||||||
navbar.sections.popular=Populare
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -243,7 +243,7 @@ database.fileNotFound=File non trovato
|
|||||||
database.fileNullOrEmpty=Il file non deve essere nullo o vuoto
|
database.fileNullOrEmpty=Il file non deve essere nullo o vuoto
|
||||||
database.failedImportFile=Importazione file non riuscita
|
database.failedImportFile=Importazione file non riuscita
|
||||||
|
|
||||||
session.expired=La tua sessione è scaduta. Aggiorna la pagina e riprova.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -503,9 +503,9 @@ home.removeImagePdf.desc=Rimuovi le immagini dal PDF per ridurre la dimensione d
|
|||||||
removeImagePdf.tags=Rimuovi immagine,operazioni sulla pagina,back-end,lato server
|
removeImagePdf.tags=Rimuovi immagine,operazioni sulla pagina,back-end,lato server
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Dividi PDF per capitoli
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Dividi un PDF in più file in base alla struttura dei capitoli.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=dividi, capitoli, segnalibri, organizza
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Sostituisci-Inverti-Colore
|
replace-color.title=Sostituisci-Inverti-Colore
|
||||||
@@ -551,9 +551,10 @@ login.oauth2AccessDenied=Accesso negato
|
|||||||
login.oauth2InvalidTokenResponse=Risposta token non valida
|
login.oauth2InvalidTokenResponse=Risposta token non valida
|
||||||
login.oauth2InvalidIdToken=Id Token non valido
|
login.oauth2InvalidIdToken=Id Token non valido
|
||||||
login.userIsDisabled=L'utente è disattivato, l'accesso è attualmente bloccato con questo nome utente. Si prega di contattare l'amministratore.
|
login.userIsDisabled=L'utente è disattivato, l'accesso è attualmente bloccato con questo nome utente. Si prega di contattare l'amministratore.
|
||||||
login.alreadyLoggedIn=Hai già effettuato l'accesso a
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=dispositivi. Esci dai dispositivi e riprova.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Hai troppe sessioni attive
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Redazione automatica
|
autoRedact.title=Redazione automatica
|
||||||
@@ -1181,8 +1182,8 @@ licenses.license=Licenza
|
|||||||
survey.nav=Sondaggio
|
survey.nav=Sondaggio
|
||||||
survey.title=Sondaggio Stirling-PDF
|
survey.title=Sondaggio Stirling-PDF
|
||||||
survey.description=Stirling-PDF non fa tracciamento, quindi vogliamo sentire i nostri utenti per migliorare Stirling-PDF!
|
survey.description=Stirling-PDF non fa tracciamento, quindi vogliamo sentire i nostri utenti per migliorare Stirling-PDF!
|
||||||
survey.changes=Stirling-PDF è cambiato dall'ultimo sondaggio! Per saperne di più, consulta il nostro blog qui:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=Con questi cambiamenti stiamo ricevendo supporto aziendale e finanziamenti retribuiti
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Ti invitiamo a prendere in considerazione la possibilità di partecipare al nostro sondaggio!
|
survey.please=Ti invitiamo a prendere in considerazione la possibilità di partecipare al nostro sondaggio!
|
||||||
survey.disabled=(Il popup del sondaggio verrà disabilitato nei prossimi aggiornamenti ma sarà disponibile a piè di pagina)
|
survey.disabled=(Il popup del sondaggio verrà disabilitato nei prossimi aggiornamenti ma sarà disponibile a piè di pagina)
|
||||||
survey.button=Partecipa al sondaggio
|
survey.button=Partecipa al sondaggio
|
||||||
@@ -1210,15 +1211,15 @@ removeImage.removeImage=Rimuovi immagine
|
|||||||
removeImage.submit=Rimuovi immagine
|
removeImage.submit=Rimuovi immagine
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Dividere PDF per capitoli
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=Dividi PDF per capitoli
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=Livello segnalibro
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=Includi Metadati
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=Consenti duplicati
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=Questo strumento divide un file PDF in più PDF in base alla struttura dei capitoli.
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=Livello segnalibro: seleziona il livello dei segnalibri da utilizzare per la suddivisione (0 per il livello superiore, 1 per il secondo livello, ecc.).
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=Includi metadati: se selezionato, i metadati del PDF originale verranno inclusi in ogni PDF diviso.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Consenti duplicati: se selezionata, consente più segnalibri sulla stessa pagina per creare PDF separati.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Dividi PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=ユーザーは非アクティブ化されており、現
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=自動塗りつぶし
|
autoRedact.title=自動塗りつぶし
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=자동 검열
|
autoRedact.title=자동 검열
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Automatisch censureren
|
autoRedact.title=Automatisch censureren
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Automatisk Sensurering
|
autoRedact.title=Automatisk Sensurering
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Rozmiar Czcionki
|
addPageNumbers.fontSize=Font Size
|
||||||
addPageNumbers.fontName=Nazwa Czcionki
|
addPageNumbers.fontName=Font Name
|
||||||
pdfPrompt=Wybierz PDF
|
pdfPrompt=Wybierz PDF
|
||||||
multiPdfPrompt=Wybierz PDF (2+)
|
multiPdfPrompt=Wybierz PDF (2+)
|
||||||
multiPdfDropPrompt=Wybierz (lub przeciągnij i puść) wszystkie dokumenty PDF
|
multiPdfDropPrompt=Wybierz (lub przeciągnij i puść) wszystkie dokumenty PDF
|
||||||
@@ -56,12 +56,12 @@ userNotFoundMessage=Brak użytkownika.
|
|||||||
incorrectPasswordMessage=Nieprawidłowe hasło.
|
incorrectPasswordMessage=Nieprawidłowe hasło.
|
||||||
usernameExistsMessage=Taki uzytkownik już istnieje.
|
usernameExistsMessage=Taki uzytkownik już istnieje.
|
||||||
invalidUsernameMessage=Niewłaściwa nazwa użytkownika - musi zawierać litery, cyfry i @._+- LUB być adresem email.
|
invalidUsernameMessage=Niewłaściwa nazwa użytkownika - musi zawierać litery, cyfry i @._+- LUB być adresem email.
|
||||||
invalidPasswordMessage=Hasło nie może być puste i nie może zawierać spacji na początku ani na końcu.
|
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
||||||
confirmPasswordErrorMessage=Wpisz poprawnie hasło w OBA pola.
|
confirmPasswordErrorMessage=Wpisz poprawnie hasło w OBA pola.
|
||||||
deleteCurrentUserMessage=Nie można usunąć zalogowanego użytkownika
|
deleteCurrentUserMessage=Nie można usunąć zalogowanego użytkownika
|
||||||
deleteUsernameExistsMessage=Nie można usunąć zalogowanego użytkownika
|
deleteUsernameExistsMessage=Nie można usunąć zalogowanego użytkownika
|
||||||
downgradeCurrentUserMessage=Nie można obniżyć roli bieżącego użytkownika
|
downgradeCurrentUserMessage=Nie można obniżyć roli bieżącego użytkownika
|
||||||
disabledCurrentUserMessage=Nie można wyłączyć bieżącego użytkownika
|
disabledCurrentUserMessage=The current user cannot be disabled
|
||||||
downgradeCurrentUserLongMessage=Nie można obniżyć roli bieżącego użytkownika. W związku z tym bieżący użytkownik nie zostanie wyświetlony.
|
downgradeCurrentUserLongMessage=Nie można obniżyć roli bieżącego użytkownika. W związku z tym bieżący użytkownik nie zostanie wyświetlony.
|
||||||
userAlreadyExistsOAuthMessage=Takie konto użytkownika istnieje - stworzone za pomocą OAuth2.
|
userAlreadyExistsOAuthMessage=Takie konto użytkownika istnieje - stworzone za pomocą OAuth2.
|
||||||
userAlreadyExistsWebMessage=Takie konto użytkownika istnieje - stworzone za pomocą przeglądarki.
|
userAlreadyExistsWebMessage=Takie konto użytkownika istnieje - stworzone za pomocą przeglądarki.
|
||||||
@@ -77,14 +77,14 @@ color=kolor
|
|||||||
sponsor=sponsor
|
sponsor=sponsor
|
||||||
info=informacje
|
info=informacje
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Strona
|
page=Page
|
||||||
pages=Strony
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Polityka Prywatności
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=Zasady i Postanowienia
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=Dostępność
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=Polityka plików cookie
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=Impresja
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
@@ -114,21 +114,21 @@ pipelineOptions.validateButton=Waliduj
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Uaktualnij do wersji Pro
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Ta funkcja jest dostępna tylko dla użytkowników Pro.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro obsługuje pliki konfiguracyjne YAML i inne funkcje SSO.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Szukasz więcej funkcji zarządzania użytkownikami? Sprawdź Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Czy chcesz ulepszyć Stirling PDF?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF ma opcję analizy, która pomaga nam udoskonalać produkt. Nie śledzimy żadnych danych osobowych ani zawartości plików.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Rozważ włączenie funkcji analitycznych, które pomogą w rozwoju Stirling-PDF i pozwolą nam lepiej zrozumieć naszych użytkowników.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Włącz analitykę
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Wyłącz analitykę
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Możesz zmienić ustawienia analityki w pliku config/settings.yml
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -138,14 +138,14 @@ navbar.darkmode=Tryb nocny
|
|||||||
navbar.language=Języki
|
navbar.language=Języki
|
||||||
navbar.settings=Ustawienia
|
navbar.settings=Ustawienia
|
||||||
navbar.allTools=Narzędzia
|
navbar.allTools=Narzędzia
|
||||||
navbar.multiTool=Narzędzie Wielofunkcyjne
|
navbar.multiTool=Multi Tools
|
||||||
navbar.sections.organize=Organizuj
|
navbar.sections.organize=Organizuj
|
||||||
navbar.sections.convertTo=Przetwórz na PDF
|
navbar.sections.convertTo=Przetwórz na PDF
|
||||||
navbar.sections.convertFrom=Przetwórz z PDF
|
navbar.sections.convertFrom=Przetwórz z PDF
|
||||||
navbar.sections.security=Podpis i bezpieczeństwo
|
navbar.sections.security=Podpis i bezpieczeństwo
|
||||||
navbar.sections.advance=Zaawansowane
|
navbar.sections.advance=Zaawansowane
|
||||||
navbar.sections.edit=Podgląd i edycja
|
navbar.sections.edit=Podgląd i edycja
|
||||||
navbar.sections.popular=Popularne
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
@@ -218,13 +218,13 @@ adminUserSettings.forceChange=Wymuś zmianę hasło po zalogowaniu
|
|||||||
adminUserSettings.submit=Zapisz użytkownika
|
adminUserSettings.submit=Zapisz użytkownika
|
||||||
adminUserSettings.changeUserRole=Zmień rolę użytkownika
|
adminUserSettings.changeUserRole=Zmień rolę użytkownika
|
||||||
adminUserSettings.authenticated=Zalogowany
|
adminUserSettings.authenticated=Zalogowany
|
||||||
adminUserSettings.editOwnProfil=Edytuj własny profil
|
adminUserSettings.editOwnProfil=Edit own profile
|
||||||
adminUserSettings.enabledUser=włączony użytkownik
|
adminUserSettings.enabledUser=enabled user
|
||||||
adminUserSettings.disabledUser=wyłączony użytkownik
|
adminUserSettings.disabledUser=disabled user
|
||||||
adminUserSettings.activeUsers=Aktywni Użytkownicy:
|
adminUserSettings.activeUsers=Active Users:
|
||||||
adminUserSettings.disabledUsers=Wyłączeni Użytkownicy:
|
adminUserSettings.disabledUsers=Disabled Users:
|
||||||
adminUserSettings.totalUsers=Łączna Liczba Użytkowników:
|
adminUserSettings.totalUsers=Total Users:
|
||||||
adminUserSettings.lastRequest=Ostatnie Zgłoszenie
|
adminUserSettings.lastRequest=Last Request
|
||||||
|
|
||||||
|
|
||||||
database.title=Import/Eksport bazy danych
|
database.title=Import/Eksport bazy danych
|
||||||
@@ -243,7 +243,7 @@ database.fileNotFound=Plik nie znaleziony
|
|||||||
database.fileNullOrEmpty=Plik nie może być pusty
|
database.fileNullOrEmpty=Plik nie może być pusty
|
||||||
database.failedImportFile=Nie udało się zaimportować pliku
|
database.failedImportFile=Nie udało się zaimportować pliku
|
||||||
|
|
||||||
session.expired=Twoja sesja wygasła. Odśwież stronę i spróbuj ponownie.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -256,207 +256,207 @@ home.viewPdf.title=Podejrzyj PDF
|
|||||||
home.viewPdf.desc=Wyświetl, adnotuj, dodaj tekst lub obrazy
|
home.viewPdf.desc=Wyświetl, adnotuj, dodaj tekst lub obrazy
|
||||||
viewPdf.tags=wyświetl,czytaj,adnotuj,tekst,obraz
|
viewPdf.tags=wyświetl,czytaj,adnotuj,tekst,obraz
|
||||||
|
|
||||||
home.multiTool.title=Wielofunkcyjne Narzędzie PDF
|
home.multiTool.title=Multi narzędzie PDF
|
||||||
home.multiTool.desc=Łącz, dziel, obracaj, zmieniaj kolejność i usuwaj strony
|
home.multiTool.desc=Łącz, dziel, obracaj, zmieniaj kolejność i usuwaj strony
|
||||||
multiTool.tags=Wielofunkcyjne narzędzie, obsługa wielu operacji, interfejs użytkownika, przeciąganie kliknięć, front-end, strona klienta
|
multiTool.tags=Multi Tool,Multi operation,UI,click drag,front end,client side
|
||||||
|
|
||||||
home.merge.title=Połącz
|
home.merge.title=Połącz
|
||||||
home.merge.desc=Łatwe łączenie wielu dokumentów PDF w jeden.
|
home.merge.desc=Łatwe łączenie wielu dokumentów PDF w jeden.
|
||||||
merge.tags=scalanie, operacje na stronach, back-end, po stronie serwera
|
merge.tags=merge,Page operations,Back end,server side
|
||||||
|
|
||||||
home.split.title=Podziel
|
home.split.title=Podziel
|
||||||
home.split.desc=Podziel dokument PDF na wiele dokumentów
|
home.split.desc=Podziel dokument PDF na wiele dokumentów
|
||||||
split.tags=Operacje na stronach, dzielenie, wiele stron, cięcie, po stronie serwera
|
split.tags=Page operations,divide,Multi Page,cut,server side
|
||||||
|
|
||||||
home.rotate.title=Obróć
|
home.rotate.title=Obróć
|
||||||
home.rotate.desc=Łatwo obracaj dokumenty PDF.
|
home.rotate.desc=Łatwo obracaj dokumenty PDF.
|
||||||
rotate.tags=strona serwera
|
rotate.tags=server side
|
||||||
|
|
||||||
|
|
||||||
home.imageToPdf.title=Obraz na PDF
|
home.imageToPdf.title=Obraz na PDF
|
||||||
home.imageToPdf.desc=Konwertuj obraz (PNG, JPEG, GIF) do dokumentu PDF.
|
home.imageToPdf.desc=Konwertuj obraz (PNG, JPEG, GIF) do dokumentu PDF.
|
||||||
imageToPdf.tags=konwersja,img,jpg,obraz,zdjęcie
|
imageToPdf.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
home.pdfToImage.title=PDF na Obraz
|
home.pdfToImage.title=PDF na Obraz
|
||||||
home.pdfToImage.desc=Konwertuj plik PDF na obraz (PNG, JPEG, GIF).
|
home.pdfToImage.desc=Konwertuj plik PDF na obraz (PNG, JPEG, GIF).
|
||||||
pdfToImage.tags=konwersja,img,jpg,obraz,zdjęcie
|
pdfToImage.tags=conversion,img,jpg,picture,photo
|
||||||
|
|
||||||
home.pdfOrganiser.title=Uporządkuj
|
home.pdfOrganiser.title=Uporządkuj
|
||||||
home.pdfOrganiser.desc=Usuń/Zmień kolejność stron w dowolnej kolejności
|
home.pdfOrganiser.desc=Usuń/Zmień kolejność stron w dowolnej kolejności
|
||||||
pdfOrganiser.tags=duplex,parzyste,nieparzyste,sortuj,przenieś
|
pdfOrganiser.tags=duplex,even,odd,sort,move
|
||||||
|
|
||||||
|
|
||||||
home.addImage.title=Dodaj obraz
|
home.addImage.title=Dodaj obraz
|
||||||
home.addImage.desc=Dodaje obraz w wybranym miejscu w dokumencie PDF
|
home.addImage.desc=Dodaje obraz w wybranym miejscu w dokumencie PDF
|
||||||
addImage.tags=img,jpg,obraz,zdjęcie
|
addImage.tags=img,jpg,picture,photo
|
||||||
|
|
||||||
home.watermark.title=Dodaj znak wodny
|
home.watermark.title=Dodaj znak wodny
|
||||||
home.watermark.desc=Dodaj niestandardowy znak wodny do dokumentu PDF.
|
home.watermark.desc=Dodaj niestandardowy znak wodny do dokumentu PDF.
|
||||||
watermark.tags=Tekst,powtarzanie,etykieta,własne,prawa autorskie,znak wodny,img,jpg,obraz,zdjęcie
|
watermark.tags=Text,repeating,label,own,copyright,trademark,img,jpg,picture,photo
|
||||||
|
|
||||||
home.permissions.title=Zmień uprawnienia
|
home.permissions.title=Zmień uprawnienia
|
||||||
home.permissions.desc=Zmień uprawnienia dokumentu PDF
|
home.permissions.desc=Zmień uprawnienia dokumentu PDF
|
||||||
permissions.tags=odczyt,zapis,edycja,drukowanie
|
permissions.tags=read,write,edit,print
|
||||||
|
|
||||||
|
|
||||||
home.removePages.title=Usuń
|
home.removePages.title=Usuń
|
||||||
home.removePages.desc=Usuń niechciane strony z dokumentu PDF.
|
home.removePages.desc=Usuń niechciane strony z dokumentu PDF.
|
||||||
removePages.tags=Usuń strony,usuwaj strony
|
removePages.tags=Remove pages,delete pages
|
||||||
|
|
||||||
home.addPassword.title=Dodaj hasło
|
home.addPassword.title=Dodaj hasło
|
||||||
home.addPassword.desc=Zaszyfruj dokument PDF za pomocą hasła.
|
home.addPassword.desc=Zaszyfruj dokument PDF za pomocą hasła.
|
||||||
addPassword.tags=bezpieczeństwo,ochrona
|
addPassword.tags=secure,security
|
||||||
|
|
||||||
home.removePassword.title=Usuń hasło
|
home.removePassword.title=Usuń hasło
|
||||||
home.removePassword.desc=Usuń ochronę hasłem z dokumentu PDF.
|
home.removePassword.desc=Usuń ochronę hasłem z dokumentu PDF.
|
||||||
removePassword.tags=zabezpieczenie,odszyfrowanie,bezpieczeństwo,odhasłowanie,usunięcie hasła
|
removePassword.tags=secure,Decrypt,security,unpassword,delete password
|
||||||
|
|
||||||
home.compressPdfs.title=Kompresuj
|
home.compressPdfs.title=Kompresuj
|
||||||
home.compressPdfs.desc=Kompresuj dokumenty PDF, aby zmniejszyć ich rozmiar.
|
home.compressPdfs.desc=Kompresuj dokumenty PDF, aby zmniejszyć ich rozmiar.
|
||||||
compressPdfs.tags=zgniatać,mały,malutki
|
compressPdfs.tags=squish,small,tiny
|
||||||
|
|
||||||
|
|
||||||
home.changeMetadata.title=Zmień metadane
|
home.changeMetadata.title=Zmień metadane
|
||||||
home.changeMetadata.desc=Zmień/Usuń/Dodaj metadane w dokumencie PDF
|
home.changeMetadata.desc=Zmień/Usuń/Dodaj metadane w dokumencie PDF
|
||||||
changeMetadata.tags=Tytuł,autor,data,utworzenie,czas,wydawca,producent,statystyki
|
changeMetadata.tags=Title,author,date,creation,time,publisher,producer,stats
|
||||||
|
|
||||||
home.fileToPDF.title=Konwertuj plik do PDF
|
home.fileToPDF.title=Konwertuj plik do PDF
|
||||||
home.fileToPDF.desc=Konwertuj dowolny plik do dokumentu PDF (DOCX, PNG, XLS, PPT, TXT i więcej)
|
home.fileToPDF.desc=Konwertuj dowolny plik do dokumentu PDF (DOCX, PNG, XLS, PPT, TXT i więcej)
|
||||||
fileToPDF.tags=transformacja,format,dokument,obraz,slajd,tekst,konwersja,office,dokumenty,word,excel,powerpoint
|
fileToPDF.tags=transformation,format,document,picture,slide,text,conversion,office,docs,word,excel,powerpoint
|
||||||
|
|
||||||
home.ocr.title=OCR / Zamiana na tekst
|
home.ocr.title=OCR / Zamiana na tekst
|
||||||
home.ocr.desc=OCR skanuje i wykrywa tekst z obrazów w dokumencie PDF i zamienia go na tekst.
|
home.ocr.desc=OCR skanuje i wykrywa tekst z obrazów w dokumencie PDF i zamienia go na tekst.
|
||||||
ocr.tags=rozpoznawanie, tekst, obraz, skanowanie, odczyt, identyfikacja, wykrywanie, edytowalność
|
ocr.tags=recognition,text,image,scan,read,identify,detection,editable
|
||||||
|
|
||||||
|
|
||||||
home.extractImages.title=Wyodrębnij obrazy
|
home.extractImages.title=Wyodrębnij obrazy
|
||||||
home.extractImages.desc=Wyodrębnia wszystkie obrazy z dokumentu PDF i zapisuje je w wybranym formacie
|
home.extractImages.desc=Wyodrębnia wszystkie obrazy z dokumentu PDF i zapisuje je w wybranym formacie
|
||||||
extractImages.tags=obraz, zdjęcie, zapisz, archiwum, zip, przechwyć, złap
|
extractImages.tags=picture,photo,save,archive,zip,capture,grab
|
||||||
|
|
||||||
home.pdfToPDFA.title=PDF na PDF/A
|
home.pdfToPDFA.title=PDF na PDF/A
|
||||||
home.pdfToPDFA.desc=Konwertuj dokument PDF na PDF/A w celu długoterminowego przechowywania
|
home.pdfToPDFA.desc=Konwertuj dokument PDF na PDF/A w celu długoterminowego przechowywania
|
||||||
pdfToPDFA.tags=archiwum, długoterminowe, standardowe, konwersja, przechowywanie, konserwacja
|
pdfToPDFA.tags=archive,long-term,standard,conversion,storage,preservation
|
||||||
|
|
||||||
home.PDFToWord.title=PDF na Word
|
home.PDFToWord.title=PDF na Word
|
||||||
home.PDFToWord.desc=Konwertuj dokument PDF na formaty Word (DOC, DOCX i ODT)
|
home.PDFToWord.desc=Konwertuj dokument PDF na formaty Word (DOC, DOCX i ODT)
|
||||||
PDFToWord.tags=doc,docx,odt,word, przekształcenie, transformacja, konwersja, office, microsoft, plik doc
|
PDFToWord.tags=doc,docx,odt,word,transformation,format,conversion,office,microsoft,docfile
|
||||||
|
|
||||||
home.PDFToPresentation.title=PDF na Prezentację
|
home.PDFToPresentation.title=PDF na Prezentację
|
||||||
home.PDFToPresentation.desc=Konwertuj dokument PDF na formaty prezentacji (PPT, PPTX i ODP)
|
home.PDFToPresentation.desc=Konwertuj dokument PDF na formaty prezentacji (PPT, PPTX i ODP)
|
||||||
PDFToPresentation.tags=slajdy, pokaz, office, microsoft
|
PDFToPresentation.tags=slides,show,office,microsoft
|
||||||
|
|
||||||
home.PDFToText.title=PDF na Tekst/RTF
|
home.PDFToText.title=PDF na Tekst/RTF
|
||||||
home.PDFToText.desc=Konwertuj dokument PDF na tekst lub format RTF
|
home.PDFToText.desc=Konwertuj dokument PDF na tekst lub format RTF
|
||||||
PDFToText.tags=format tekstu sformatowanego,rtf format
|
PDFToText.tags=richformat,richtextformat,rich text format
|
||||||
|
|
||||||
home.PDFToHTML.title=PDF na HTML
|
home.PDFToHTML.title=PDF na HTML
|
||||||
home.PDFToHTML.desc=Konwertuj dokument PDF na format HTML
|
home.PDFToHTML.desc=Konwertuj dokument PDF na format HTML
|
||||||
PDFToHTML.tags=zawartość internetowa, przyjazne dla przeglądarek
|
PDFToHTML.tags=web content,browser friendly
|
||||||
|
|
||||||
|
|
||||||
home.PDFToXML.title=PDF na XML
|
home.PDFToXML.title=PDF na XML
|
||||||
home.PDFToXML.desc=Konwertuj dokument PDF na format XML
|
home.PDFToXML.desc=Konwertuj dokument PDF na format XML
|
||||||
PDFToXML.tags=ekstrakcja danych, zawartość strukturalna, współdziałanie, transformacja, konwertowanie
|
PDFToXML.tags=data-extraction,structured-content,interop,transformation,convert
|
||||||
|
|
||||||
home.ScannerImageSplit.title=Wykryj/Podziel zeskanowane zdjęcia
|
home.ScannerImageSplit.title=Wykryj/Podziel zeskanowane zdjęcia
|
||||||
home.ScannerImageSplit.desc=Podziel na wiele zdjęć z jednego zdjęcia/PDF
|
home.ScannerImageSplit.desc=Podziel na wiele zdjęć z jednego zdjęcia/PDF
|
||||||
ScannerImageSplit.tags=oddzielne, automatyczne wykrywanie, skanowanie, wiele zdjęć, porządkowanie
|
ScannerImageSplit.tags=separate,auto-detect,scans,multi-photo,organize
|
||||||
|
|
||||||
home.sign.title=Podpis
|
home.sign.title=Podpis
|
||||||
home.sign.desc=Dodaje podpis do dokumentu PDF za pomocą rysunku, tekstu lub obrazu
|
home.sign.desc=Dodaje podpis do dokumentu PDF za pomocą rysunku, tekstu lub obrazu
|
||||||
sign.tags=autoryzacja, inicjały, podpis odręczny, podpis tekstowy, podpis graficzny
|
sign.tags=authorize,initials,drawn-signature,text-sign,image-signature
|
||||||
|
|
||||||
home.flatten.title=Spłaszcz
|
home.flatten.title=Spłaszcz
|
||||||
home.flatten.desc=Usuń wszystkie interaktywne elementy i formularze z dokumentu PDF
|
home.flatten.desc=Usuń wszystkie interaktywne elementy i formularze z dokumentu PDF
|
||||||
flatten.tags=statyczny, dezaktywacja, nieinteraktywny, opływowy, streamline
|
flatten.tags=static,deactivate,non-interactive,streamline
|
||||||
|
|
||||||
home.repair.title=Napraw
|
home.repair.title=Napraw
|
||||||
home.repair.desc=Spróbuj naprawić uszkodzony dokument PDF
|
home.repair.desc=Spróbuj naprawić uszkodzony dokument PDF
|
||||||
repair.tags=naprawianie, naprawa, przywracanie, poprawianie, odzyskiwanie
|
repair.tags=fix,restore,correction,recover
|
||||||
|
|
||||||
home.removeBlanks.title=Usuń puste strony
|
home.removeBlanks.title=Usuń puste strony
|
||||||
home.removeBlanks.desc=Wykrywa i usuwa puste strony z dokumentu PDF
|
home.removeBlanks.desc=Wykrywa i usuwa puste strony z dokumentu PDF
|
||||||
removeBlanks.tags=czyszczenie, usprawnianie, brak treści, organizowanie
|
removeBlanks.tags=cleanup,streamline,non-content,organize
|
||||||
|
|
||||||
home.removeAnnotations.title=Usuń notatki/przypisy
|
home.removeAnnotations.title=Usuń notatki/przypisy
|
||||||
home.removeAnnotations.desc=Usuwa wszystkie notatki i przypisy z dokumentu PDF
|
home.removeAnnotations.desc=Usuwa wszystkie notatki i przypisy z dokumentu PDF
|
||||||
removeAnnotations.tags=komentarze, podświetlanie, notatki, znaczniki, usuwanie
|
removeAnnotations.tags=comments,highlight,notes,markup,remove
|
||||||
|
|
||||||
home.compare.title=Porównaj
|
home.compare.title=Porównaj
|
||||||
home.compare.desc=Porównuje i pokazuje różnice między dwoma dokumentami PDF
|
home.compare.desc=Porównuje i pokazuje różnice między dwoma dokumentami PDF
|
||||||
compare.tags=rozróżnienie, kontrast, zmiany, analiza
|
compare.tags=differentiate,contrast,changes,analysis
|
||||||
|
|
||||||
home.certSign.title=Podpisz certyfikatem
|
home.certSign.title=Podpisz certyfikatem
|
||||||
home.certSign.desc=Podpisz dokument PDF za pomocą certyfikatu/klucza prywatnego (PEM/P12)
|
home.certSign.desc=Podpisz dokument PDF za pomocą certyfikatu/klucza prywatnego (PEM/P12)
|
||||||
certSign.tags=uwierzytelnianie, PEM, P12, oficjalny, szyfrowanie
|
certSign.tags=authenticate,PEM,P12,official,encrypt
|
||||||
|
|
||||||
home.removeCertSign.title=Usuń podpis certyfikatem
|
home.removeCertSign.title=Usuń podpis certyfikatem
|
||||||
home.removeCertSign.desc=Usuń podpis certyfikatem z dokumentu PDF
|
home.removeCertSign.desc=Usuń podpis certyfikatem z dokumentu PDF
|
||||||
removeCertSign.tags=uwierzytelnianie, PEM, P12, oficjalny, odszyfrowywanie
|
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=Układ wielu stron
|
home.pageLayout.title=Układ wielu stron
|
||||||
home.pageLayout.desc=Scal wiele stron dokumentu PDF w jedną stronę
|
home.pageLayout.desc=Scal wiele stron dokumentu PDF w jedną stronę
|
||||||
pageLayout.tags=scalanie, kompozycja, pojedynczy widok, organizowanie, porządkowanie
|
pageLayout.tags=merge,composite,single-view,organize
|
||||||
|
|
||||||
home.scalePages.title=Dopasuj rozmiar stron
|
home.scalePages.title=Dopasuj rozmiar stron
|
||||||
home.scalePages.desc=Dopasuj rozmiar stron wybranego dokumentu PDF
|
home.scalePages.desc=Dopasuj rozmiar stron wybranego dokumentu PDF
|
||||||
scalePages.tags=zmiana rozmiaru, modyfikacja, rozmiar, dostosowanie
|
scalePages.tags=resize,modify,dimension,adapt
|
||||||
|
|
||||||
home.pipeline.title=Automatyzacja
|
home.pipeline.title=Automatyzacja
|
||||||
home.pipeline.desc=Wykonaj wiele akcji na dokumentach PDF, tworząc automatyzację
|
home.pipeline.desc=Wykonaj wiele akcji na dokumentach PDF, tworząc automatyzację
|
||||||
pipeline.tags=automatyzacja, sekwencja, skrypt, przetwarzanie wsadowe
|
pipeline.tags=automate,sequence,scripted,batch-process
|
||||||
|
|
||||||
home.add-page-numbers.title=Dodaj numery stron
|
home.add-page-numbers.title=Dodaj numery stron
|
||||||
home.add-page-numbers.desc=Dodaj numery strony w dokumencie PDF w podanej lokalizacji
|
home.add-page-numbers.desc=Dodaj numery strony w dokumencie PDF w podanej lokalizacji
|
||||||
add-page-numbers.tags=stronicowanie, etykieta, organizowanie, indeks, index
|
add-page-numbers.tags=paginate,label,organize,index
|
||||||
|
|
||||||
home.auto-rename.title=Automatycznie zmień nazwę PDF
|
home.auto-rename.title=Automatycznie zmień nazwę PDF
|
||||||
home.auto-rename.desc=Automatycznie zmień nazwę PDF bazując na nagłówku
|
home.auto-rename.desc=Automatycznie zmień nazwę PDF bazując na nagłówku
|
||||||
auto-rename.tags=automatyczne wykrywanie, oparte na nagłówkach, organizowanie, ponowne etykietowanie
|
auto-rename.tags=auto-detect,header-based,organize,relabel
|
||||||
|
|
||||||
home.adjust-contrast.title=Zmień kolor/nasycenie/jasność
|
home.adjust-contrast.title=Zmień kolor/nasycenie/jasność
|
||||||
home.adjust-contrast.desc=Zmień kolor/nasycenie/jasność w dokumencie PDF
|
home.adjust-contrast.desc=Zmień kolor/nasycenie/jasność w dokumencie PDF
|
||||||
adjust-contrast.tags=Korekcja kolorów, dostrajanie, modyfikacja, ulepszanie
|
adjust-contrast.tags=color-correction,tune,modify,enhance
|
||||||
|
|
||||||
home.crop.title=Przytnij PDF
|
home.crop.title=Przytnij PDF
|
||||||
home.crop.desc=Przytnij dokument PDF w celu zmniejszenia rozmiaru
|
home.crop.desc=Przytnij dokument PDF w celu zmniejszenia rozmiaru
|
||||||
crop.tags=przycinanie, zmniejszanie, edycja, kształtowanie
|
crop.tags=trim,shrink,edit,shape
|
||||||
|
|
||||||
home.autoSplitPDF.title=Automatycznie podziel strony
|
home.autoSplitPDF.title=Automatycznie podziel strony
|
||||||
home.autoSplitPDF.desc=Automatycznie podziel dokument na strony
|
home.autoSplitPDF.desc=Automatycznie podziel dokument na strony
|
||||||
autoSplitPDF.tags=Oparty na QR, rozdzielanie, skanowanie, organizowanie
|
autoSplitPDF.tags=QR-based,separate,scan-segment,organize
|
||||||
|
|
||||||
home.sanitizePdf.title=Dezynfekcja
|
home.sanitizePdf.title=Dezynfekcja
|
||||||
home.sanitizePdf.desc=Usuń skrypt i inne elementy z dokumentu PDF
|
home.sanitizePdf.desc=Usuń skrypt i inne elementy z dokumentu PDF
|
||||||
sanitizePdf.tags=czyszczenie, ochrona, bezpieczeństwo, usuwanie zagrożeń
|
sanitizePdf.tags=clean,secure,safe,remove-threats
|
||||||
|
|
||||||
home.URLToPDF.title=Strona WWW do PDFa
|
home.URLToPDF.title=Strona WWW do PDFa
|
||||||
home.URLToPDF.desc=Zapisuje podany adres WWW do PDFa
|
home.URLToPDF.desc=Zapisuje podany adres WWW do PDFa
|
||||||
URLToPDF.tags=przechwytywanie stron internetowych, zapisywanie strony, strona internetowa do dokumentu, archiwizacja
|
URLToPDF.tags=web-capture,save-page,web-to-doc,archive
|
||||||
|
|
||||||
home.HTMLToPDF.title=HTML do PDF
|
home.HTMLToPDF.title=HTML do PDF
|
||||||
home.HTMLToPDF.desc=Zapisuje podany plik HTML/ZIP do PDF
|
home.HTMLToPDF.desc=Zapisuje podany plik HTML/ZIP do PDF
|
||||||
HTMLToPDF.tags=znaczniki, treść internetowa, transformacja, konwertowanie
|
HTMLToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
home.MarkdownToPDF.title=Markdown do PDF
|
home.MarkdownToPDF.title=Markdown do PDF
|
||||||
home.MarkdownToPDF.desc=Zapisuje dokument Markdown do PDF
|
home.MarkdownToPDF.desc=Zapisuje dokument Markdown do PDF
|
||||||
MarkdownToPDF.tags=znaczniki, treść internetowa, transformacja, konwertowanie
|
MarkdownToPDF.tags=markup,web-content,transformation,convert
|
||||||
|
|
||||||
|
|
||||||
home.getPdfInfo.title=Pobierz informacje o pliku PDF
|
home.getPdfInfo.title=Pobierz informacje o pliku PDF
|
||||||
home.getPdfInfo.desc=Pobiera wszelkie informacje o pliku PDF
|
home.getPdfInfo.desc=Pobiera wszelkie informacje o pliku PDF
|
||||||
getPdfInfo.tags=informacje, dane, statystyka, statystyki
|
getPdfInfo.tags=infomation,data,stats,statistics
|
||||||
|
|
||||||
|
|
||||||
home.extractPage.title=Wyciągnij stronę z PDF
|
home.extractPage.title=Wyciągnij stronę z PDF
|
||||||
home.extractPage.desc=Wyciąga stronę z dokumentu PDF
|
home.extractPage.desc=Wyciąga stronę z dokumentu PDF
|
||||||
extractPage.tags=wydobycie,separacja,wyciaganie
|
extractPage.tags=extract
|
||||||
|
|
||||||
|
|
||||||
home.PdfToSinglePage.title=PDF do jednej strony
|
home.PdfToSinglePage.title=PDF do jednej strony
|
||||||
home.PdfToSinglePage.desc=Łączy wszystkie strony PDFa w jedną wielką stronę PDF
|
home.PdfToSinglePage.desc=Łączy wszystkie strony PDFa w jedną wielką stronę PDF
|
||||||
PdfToSinglePage.tags=pojedyncza strona
|
PdfToSinglePage.tags=single page
|
||||||
|
|
||||||
|
|
||||||
home.showJS.title=Pokaż kod JavaScript
|
home.showJS.title=Pokaż kod JavaScript
|
||||||
@@ -465,66 +465,66 @@ showJS.tags=JS
|
|||||||
|
|
||||||
home.autoRedact.title=Zaciemnij
|
home.autoRedact.title=Zaciemnij
|
||||||
home.autoRedact.desc=Zaciemnia dokument PDF bazując na podanej wartości
|
home.autoRedact.desc=Zaciemnia dokument PDF bazując na podanej wartości
|
||||||
autoRedact.tags=Redagowanie, ukrywanie, zaciemnianie, zaczernianie, zaznaczanie, ukrywanie
|
autoRedact.tags=Redact,Hide,black out,black,marker,hidden
|
||||||
|
|
||||||
home.tableExtraxt.title=PDF do CSV
|
home.tableExtraxt.title=PDF do CSV
|
||||||
home.tableExtraxt.desc=Konwertuje tabele z PDF do pliku CSV
|
home.tableExtraxt.desc=Konwertuje tabele z PDF do pliku CSV
|
||||||
tableExtraxt.tags=CSV, ekstrakcja tabeli, ekstrakcja, konwersja, wydobywanie
|
tableExtraxt.tags=CSV,Table Extraction,extract,convert
|
||||||
|
|
||||||
|
|
||||||
home.autoSizeSplitPDF.title=Podziel (Rozmiar/Ilość stron)
|
home.autoSizeSplitPDF.title=Podziel (Rozmiar/Ilość stron)
|
||||||
home.autoSizeSplitPDF.desc=Rozdziela dokument PDF na wiele dokumentów bazując na podanym rozmiarze, ilości stron bądź ilości dokumentów
|
home.autoSizeSplitPDF.desc=Rozdziela dokument PDF na wiele dokumentów bazując na podanym rozmiarze, ilości stron bądź ilości dokumentów
|
||||||
autoSizeSplitPDF.tags=pdf, dzielenie, dokument, organizacja
|
autoSizeSplitPDF.tags=pdf,split,document,organization
|
||||||
|
|
||||||
|
|
||||||
home.overlay-pdfs.title=Nałóż PDFa
|
home.overlay-pdfs.title=Nałóż PDFa
|
||||||
home.overlay-pdfs.desc=Nakłada dokumenty PDF na siebie
|
home.overlay-pdfs.desc=Nakłada dokumenty PDF na siebie
|
||||||
overlay-pdfs.tags=Nakładka
|
overlay-pdfs.tags=Overlay
|
||||||
|
|
||||||
home.split-by-sections.title=Podziel PDF na sekcje
|
home.split-by-sections.title=Podziel PDF na sekcje
|
||||||
home.split-by-sections.desc=Podziel strony PDF w mniejsze sekcje
|
home.split-by-sections.desc=Podziel strony PDF w mniejsze sekcje
|
||||||
split-by-sections.tags=Podział sekcji, dzielenie, dostosowywanie
|
split-by-sections.tags=Section Split, Divide, Customize
|
||||||
|
|
||||||
home.AddStampRequest.title=Dodaj pieczęć
|
home.AddStampRequest.title=Dodaj pieczęć
|
||||||
home.AddStampRequest.desc=Dodaj pieczęć tekstową/obrazową w wyznaczonej lokalizacji dokumentu
|
home.AddStampRequest.desc=Dodaj pieczęć tekstową/obrazową w wyznaczonej lokalizacji dokumentu
|
||||||
AddStampRequest.tags=Stempel, dodawanie obrazu, wyśrodkowanie obrazu, znak wodny, PDF, osadzanie, dostosowywanie
|
AddStampRequest.tags=Stamp, Add image, center image, Watermark, PDF, Embed, Customize
|
||||||
|
|
||||||
|
|
||||||
home.PDFToBook.title=PDF do eBooka
|
home.PDFToBook.title=PDF do eBooka
|
||||||
home.PDFToBook.desc=Zapisuje dokument PDF w formacie eBooka za pomocą Calibre
|
home.PDFToBook.desc=Zapisuje dokument PDF w formacie eBooka za pomocą Calibre
|
||||||
PDFToBook.tags=Książka,komiks,Calibre, konwertowanie, manga, amazon, kindle
|
PDFToBook.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||||
|
|
||||||
home.BookToPDF.title=eBook do PDF
|
home.BookToPDF.title=eBook do PDF
|
||||||
home.BookToPDF.desc=Zapisuje ebooka do PDF za pomocą Calibre
|
home.BookToPDF.desc=Zapisuje ebooka do PDF za pomocą Calibre
|
||||||
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
BookToPDF.tags=Book,Comic,Calibre,Convert,manga,amazon,kindle
|
||||||
|
|
||||||
home.removeImagePdf.title=Usuń obraz
|
home.removeImagePdf.title=Remove image
|
||||||
home.removeImagePdf.desc=Usuń obraz z pliku PDF, aby zmniejszyć rozmiar pliku
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=Usuń obraz, operacje na stronie, back-end, strona serwera
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Podziel PDF według rozdziałów
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Podział pliku PDF na wiele plików na podstawie struktury rozdziałów.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=podział, rozdziały, zakładki, porządkowanie, organizacja
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Zamień-Odwróć-Kolor
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Zamień-Odwróć kolor PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Zastąp i Odwróć Kolor
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Zastąp kolor tekstu i tła w pliku PDF i odwróć pełen kolor pliku PDF, aby zmniejszyć rozmiar pliku
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Zastąp kolor, operacje na stronach, back-end, strona serwera
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Zastąp lub Odwróć opcje kolorów
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=Domyślnie (domyślne kolory o wysokim kontraście)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=Niestandardowe (kolory niestandardowe)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Całkowita-Odwrotność (Odwrócenie wszystkich kolorów)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Wysoki kontrast opcji kolorystycznych
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=biały tekst na czarnym tle
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Czarny tekst na białym tle
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Żółty tekst na czarnym tle
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Zielony tekst na czarnym tle
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Wybierz Kolor tekstu
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Wybierz Kolor tła
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Zamień
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -543,17 +543,18 @@ login.locked=Konto jest zablokowane
|
|||||||
login.signinTitle=Zaloguj się
|
login.signinTitle=Zaloguj się
|
||||||
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
login.ssoSignIn=Zaloguj się za pomocą logowania jednokrotnego
|
||||||
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
login.oauth2AutoCreateDisabled=Wyłączono automatyczne tworzenie użytkownika OAUTH2
|
||||||
login.oauth2AdminBlockedUser=Rejestracja lub logowanie niezarejestrowanych użytkowników jest obecnie zablokowane. Prosimy o kontakt z administratorem.
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=Błąd logowania OAuth2
|
login.oauth2RequestNotFound=Błąd logowania OAuth2
|
||||||
login.oauth2InvalidUserInfoResponse=Niewłaściwe dane logowania
|
login.oauth2InvalidUserInfoResponse=Niewłaściwe dane logowania
|
||||||
login.oauth2invalidRequest=Nieprawidłowe żądanie
|
login.oauth2invalidRequest=Nieprawidłowe żądanie
|
||||||
login.oauth2AccessDenied=Brak dostępu
|
login.oauth2AccessDenied=Brak dostępu
|
||||||
login.oauth2InvalidTokenResponse=Nieprawidłowa odpowiedź na token
|
login.oauth2InvalidTokenResponse=Nieprawidłowa odpowiedź na token
|
||||||
login.oauth2InvalidIdToken=Nieprawidłowa wartość tokenu
|
login.oauth2InvalidIdToken=Nieprawidłowa wartość tokenu
|
||||||
login.userIsDisabled=Użytkownik jest nieaktywny, logowanie przy użyciu tej nazwy użytkownika jest obecnie zablokowane. Prosimy o kontakt z administratorem.
|
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||||
login.alreadyLoggedIn=Jesteś już zalogowany na
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=urządzeniach. Wyloguj się z tych urządzeń i spróbuj ponownie.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Masz zbyt wiele aktywnych sesji
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Automatyczne zaciemnienie
|
autoRedact.title=Automatyczne zaciemnienie
|
||||||
@@ -777,8 +778,8 @@ removeAnnotations.submit=Usuń
|
|||||||
#compare
|
#compare
|
||||||
compare.title=Porównaj
|
compare.title=Porównaj
|
||||||
compare.header=Porównaj PDF(y)
|
compare.header=Porównaj PDF(y)
|
||||||
compare.highlightColor.1=Kolor Podświetlenia 1:
|
compare.highlightColor.1=Highlight Color 1:
|
||||||
compare.highlightColor.2=Kolor Podświetlenia 2:
|
compare.highlightColor.2=Highlight Color 2:
|
||||||
compare.document.1=Dokument 1
|
compare.document.1=Dokument 1
|
||||||
compare.document.2=Dokument 2
|
compare.document.2=Dokument 2
|
||||||
compare.submit=Porównaj
|
compare.submit=Porównaj
|
||||||
@@ -830,7 +831,7 @@ ScannerImageSplit.selectText.7=Minimalny obszar konturu:
|
|||||||
ScannerImageSplit.selectText.8=Ustawia próg minimalnego obszaru konturu dla zdjęcia
|
ScannerImageSplit.selectText.8=Ustawia próg minimalnego obszaru konturu dla zdjęcia
|
||||||
ScannerImageSplit.selectText.9=Rozmiar obramowania:
|
ScannerImageSplit.selectText.9=Rozmiar obramowania:
|
||||||
ScannerImageSplit.selectText.10=Ustawia rozmiar dodawanego i usuwanego obramowania, aby uniknąć białych obramowań na wyjściu (domyślnie: 1).
|
ScannerImageSplit.selectText.10=Ustawia rozmiar dodawanego i usuwanego obramowania, aby uniknąć białych obramowań na wyjściu (domyślnie: 1).
|
||||||
ScannerImageSplit.info=Python nie został zainstalowany. Jest on wymagany do uruchomienia.
|
ScannerImageSplit.info=Python is not installed. It is required to run.
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
@@ -857,7 +858,7 @@ ocr.submit=Przetwarzaj PDF za pomocą OCR
|
|||||||
extractImages.title=Wyodrębnij obrazy
|
extractImages.title=Wyodrębnij obrazy
|
||||||
extractImages.header=Wyodrębnij obrazy
|
extractImages.header=Wyodrębnij obrazy
|
||||||
extractImages.selectText=Wybierz format obrazu, na który chcesz przekonwertować wyodrębniony obraz.
|
extractImages.selectText=Wybierz format obrazu, na który chcesz przekonwertować wyodrębniony obraz.
|
||||||
extractImages.allowDuplicates=Zapisz zduplikowane obrazy
|
extractImages.allowDuplicates=Save duplicate images
|
||||||
extractImages.submit=Wyodrębnij
|
extractImages.submit=Wyodrębnij
|
||||||
|
|
||||||
|
|
||||||
@@ -918,8 +919,8 @@ pdfOrganiser.placeholder=(przykład 1,3,2 lub 4-8,2,10-12 lub 2n-1)
|
|||||||
|
|
||||||
|
|
||||||
#multiTool
|
#multiTool
|
||||||
multiTool.title=Narzędzie Wielofunkcyjne PDF
|
multiTool.title=Multi narzędzie PDF
|
||||||
multiTool.header=Narzędzie Wielofunkcyjne PDF
|
multiTool.header=Multi narzędzie PDF
|
||||||
multiTool.uploadPrompts=Nazwa pliku
|
multiTool.uploadPrompts=Nazwa pliku
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
@@ -982,7 +983,7 @@ pdfToImage.color=Kolor
|
|||||||
pdfToImage.grey=Odcień szarości
|
pdfToImage.grey=Odcień szarości
|
||||||
pdfToImage.blackwhite=Czarno-biały (może spowodować utratę danych!)
|
pdfToImage.blackwhite=Czarno-biały (może spowodować utratę danych!)
|
||||||
pdfToImage.submit=Konwertuj
|
pdfToImage.submit=Konwertuj
|
||||||
pdfToImage.info=Python nie został zainstalowany. Jest wymagany do konwersji WebP.
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -1019,7 +1020,7 @@ watermark.selectText.6=Odstęp w pionie (odstęp między każdym znakiem wodnym
|
|||||||
watermark.selectText.7=Nieprzezroczystość (0% - 100%):
|
watermark.selectText.7=Nieprzezroczystość (0% - 100%):
|
||||||
watermark.selectText.8=Typ znaku wodnego:
|
watermark.selectText.8=Typ znaku wodnego:
|
||||||
watermark.selectText.9=Obraz znaku wodnego:
|
watermark.selectText.9=Obraz znaku wodnego:
|
||||||
watermark.selectText.10=Konwertuj PDF do PDF-Image
|
watermark.selectText.10=Convert PDF to PDF-Image
|
||||||
watermark.submit=Dodaj znak wodny
|
watermark.submit=Dodaj znak wodny
|
||||||
watermark.type.1=Tekst
|
watermark.type.1=Tekst
|
||||||
watermark.type.2=Obraz
|
watermark.type.2=Obraz
|
||||||
@@ -1119,7 +1120,7 @@ PDFToXML.submit=Konwertuj
|
|||||||
#PDFToCSV
|
#PDFToCSV
|
||||||
PDFToCSV.title=PDF na CSV
|
PDFToCSV.title=PDF na CSV
|
||||||
PDFToCSV.header=PDF na CSV
|
PDFToCSV.header=PDF na CSV
|
||||||
PDFToCSV.prompt=Wybierz stronę do wyodrębnienia tabeli
|
PDFToCSV.prompt=Choose page to extract table
|
||||||
PDFToCSV.submit=Zatwierdź
|
PDFToCSV.submit=Zatwierdź
|
||||||
|
|
||||||
#split-by-size-or-count
|
#split-by-size-or-count
|
||||||
@@ -1181,8 +1182,8 @@ licenses.license=Licencja
|
|||||||
survey.nav=Ankieta
|
survey.nav=Ankieta
|
||||||
survey.title=Ankieta Stirling-PDF
|
survey.title=Ankieta Stirling-PDF
|
||||||
survey.description=Stirling-PDF nie śledzi swoich użytkowników, więc chciałby poznać opinie swoich użytkowników!
|
survey.description=Stirling-PDF nie śledzi swoich użytkowników, więc chciałby poznać opinie swoich użytkowników!
|
||||||
survey.changes=Stirling-PDF zmieniło się od czasu ostatniej ankiety! Aby dowiedzieć się więcej, sprawdź nasz wpis na blogu tutaj:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=Dzięki tym zmianom otrzymujemy płatne wsparcie biznesowe i finansowanie
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Wypełnij proszę ankietę dla nas!
|
survey.please=Wypełnij proszę ankietę dla nas!
|
||||||
survey.disabled=(Blokada wyskakującego okienka z ankieta zostanie dodane w następnych aktualizacjach, ale będzie dostępna na dole strony)
|
survey.disabled=(Blokada wyskakującego okienka z ankieta zostanie dodane w następnych aktualizacjach, ale będzie dostępna na dole strony)
|
||||||
survey.button=Wypełnij ankietę
|
survey.button=Wypełnij ankietę
|
||||||
@@ -1204,21 +1205,21 @@ error.discordSubmit=Discord - wyślij posta z prośbą o pomoc
|
|||||||
|
|
||||||
|
|
||||||
#remove-image
|
#remove-image
|
||||||
removeImage.title=Usuń obraz
|
removeImage.title=Remove image
|
||||||
removeImage.header=Usuń obraz
|
removeImage.header=Remove image
|
||||||
removeImage.removeImage=Usuń obraz
|
removeImage.removeImage=Remove image
|
||||||
removeImage.submit=Usuń obraz
|
removeImage.submit=Remove image
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Podziel PDF według Rozdziałów
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=Podziel PDF według Rozdziałów
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=Poziom Zakładek
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=Dołącz Metadane
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=Zezwalaj na Duplikaty
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=Narzędzie to dzieli plik PDF na wiele plików PDF w oparciu o strukturę rozdziałów.
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=Poziom Zakładek: Wybierz poziom zakładek, który ma zostać użyty do podziału (0 dla najwyższego poziomu, 1 dla drugiego poziomu itd.).
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=Dołącz Metadane: Jeśli opcja ta jest zaznaczona, metadane oryginalnego pliku PDF zostaną uwzględnione w każdym rozdzielonych plików PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Zezwól na Duplikaty: Jeśli ta opcja jest zaznaczona, pozwala na tworzenie oddzielnych plików PDF przez wiele zakładek na tej samej stronie.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Podziel PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -3,42 +3,42 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=Tamanho da Fonte
|
addPageNumbers.fontSize=Tamanho da fonte
|
||||||
addPageNumbers.fontName=Nome da Fonte
|
addPageNumbers.fontName=Nome da fonte
|
||||||
pdfPrompt=Selecione PDF(s)
|
pdfPrompt=Selecione PDF(s)
|
||||||
multiPdfPrompt=Selecione PDFs (2+)
|
multiPdfPrompt=Selecione PDFs (2+)
|
||||||
multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs desejados
|
multiPdfDropPrompt=Selecione (ou arraste e solte) todos os PDFs necessários
|
||||||
imgPrompt=Selecione a(s) Imagem(ns)
|
imgPrompt=Selecione a(s) imagem(ns)
|
||||||
genericSubmit=Enviar
|
genericSubmit=Enviar
|
||||||
processTimeWarning=Aviso: esse processo pode levar até um minuto, dependendo do tamanho do arquivo
|
processTimeWarning=Aviso: esse processo pode levar até um minuto, dependendo do tamanho do arquivo
|
||||||
pageOrderPrompt=Ordem de Página Personalizada (Digite uma lista de números de páginas, separadas por vírgula ou Funções como 2n+1):
|
pageOrderPrompt=Ordem de página personalizada (digite uma lista de números de página separada por vírgula):
|
||||||
pageSelectionPrompt=Seleção de Página Personalizada (Digite uma lista de números de páginas, separadas por vírgula 1,5,6 ou Funções como 2n+1) :
|
pageSelectionPrompt=Seleção de página personalizada (digite uma lista de números de página separada por vírgula 1,5,6 ou Funções como 2n+1) :
|
||||||
goToPage=Ir
|
goToPage=Ir
|
||||||
true=Verdadeiro
|
true=Verdadeiro
|
||||||
false=Falso
|
false=Falso
|
||||||
unknown=Desconhecido
|
unknown=Desconhecido
|
||||||
save=Salvar
|
save=Salvar
|
||||||
saveToBrowser=Salvar no Navegador
|
saveToBrowser=Salvar no navegador
|
||||||
close=Fechar
|
close=Fechar
|
||||||
filesSelected=Arquivos Selecionados
|
filesSelected=arquivos selecionados
|
||||||
noFavourites=Nenhum Favorito Adicionado
|
noFavourites=Nenhum favorito adicionado
|
||||||
downloadComplete=Download Completo
|
downloadComplete=Download Completo
|
||||||
bored=Entediado Esperando?
|
bored=Entediado esperando?
|
||||||
alphabet=Alfabeto
|
alphabet=Alfabeto
|
||||||
downloadPdf=Baixar PDF
|
downloadPdf=Baixar PDF
|
||||||
text=Texto
|
text=Texto
|
||||||
font=Fonte
|
font=Fonte
|
||||||
selectFillter=-- Selecione --
|
selectFillter=-- Selecione --
|
||||||
pageNum=Número da Página
|
pageNum=Número da página
|
||||||
sizes.small=Pequeno
|
sizes.small=Pequeno
|
||||||
sizes.medium=Médio
|
sizes.medium=Médio
|
||||||
sizes.large=Grande
|
sizes.large=Grande
|
||||||
sizes.x-large=Extra grande
|
sizes.x-large=Extra grande
|
||||||
error.pdfPassword=O documento PDF está protegido por senha e a senha não foi fornecida ou está incorreta
|
error.pdfPassword=O documento PDF está protegido por senha e a senha não foi fornecida ou está incorreta
|
||||||
delete=Apagar
|
delete=apagar
|
||||||
username=Usuário
|
username=Usuário
|
||||||
password=Senha
|
password=Senha
|
||||||
welcome=Bem-vindo
|
welcome=Bem vindo
|
||||||
property=Propriedade
|
property=Propriedade
|
||||||
black=Preto
|
black=Preto
|
||||||
white=Branco
|
white=Branco
|
||||||
@@ -46,7 +46,7 @@ red=Vermelho
|
|||||||
green=Verde
|
green=Verde
|
||||||
blue=Azul
|
blue=Azul
|
||||||
custom=Personalizado...
|
custom=Personalizado...
|
||||||
WorkInProgess=Trabalho em progresso, Talvez não funcione ou apresente erros, Por favor, reporte qualquer problema!
|
WorkInProgess=Em progesso, Talvez não funcione ou apresente erros, Por favor, reporte qualquer problema!
|
||||||
poweredBy=Distribuído por
|
poweredBy=Distribuído por
|
||||||
yes=Sim
|
yes=Sim
|
||||||
no=Não
|
no=Não
|
||||||
@@ -55,30 +55,30 @@ notAuthenticatedMessage=Usuário não autenticado.
|
|||||||
userNotFoundMessage=Usuário não encontrado.
|
userNotFoundMessage=Usuário não encontrado.
|
||||||
incorrectPasswordMessage=A senha atual está incorreta.
|
incorrectPasswordMessage=A senha atual está incorreta.
|
||||||
usernameExistsMessage=Novo Usuário já existe.
|
usernameExistsMessage=Novo Usuário já existe.
|
||||||
invalidUsernameMessage=Usuário inválido, nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um e-mail válido.
|
invalidUsernameMessage=Usuário inválido, nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um email válido.
|
||||||
invalidPasswordMessage=A senha não deve estar vazia e não deve conter espaços no início ou no final.
|
invalidPasswordMessage=A senha não deve estar vazia e não deve conter espaços no início ou no final.
|
||||||
confirmPasswordErrorMessage=Nova Senha e Confirmar Nova Senha devem ser iguais.
|
confirmPasswordErrorMessage=Nova Senha e Confirmar Nova Senha devem ser iguais.
|
||||||
deleteCurrentUserMessage=Não é possível apagar usuário conectado no momento.
|
deleteCurrentUserMessage=Não é possível apagar o usuário da sessão atual.
|
||||||
deleteUsernameExistsMessage=O usuário não existe e não pode ser apagado.
|
deleteUsernameExistsMessage=O usuário não existe e não pode ser apagado.
|
||||||
downgradeCurrentUserMessage=Não é possível fazer downgrade da função do usuário conectado no momento.
|
downgradeCurrentUserMessage=Não é possível fazer downgrade da função do usuário atual
|
||||||
disabledCurrentUserMessage=O usuário atual não pode ser desativado.
|
disabledCurrentUserMessage=O usuário atual não pode ser desativado
|
||||||
downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do usuário atual. Portanto, o usuário atual não será mostrado.
|
downgradeCurrentUserLongMessage=Não é possível fazer downgrade da função do usuário atual. Portanto, o usuário atual não será mostrado.
|
||||||
userAlreadyExistsOAuthMessage=O usuário já existe como um usuário OAuth2.
|
userAlreadyExistsOAuthMessage=O usuário já existe como um usuário OAuth2.
|
||||||
userAlreadyExistsWebMessage=O usuário já existe como um usuário Web.
|
userAlreadyExistsWebMessage=O usuário já existe como um usuário web.
|
||||||
error=Erro
|
error=Erro
|
||||||
oops=Ops!
|
oops=Ops!
|
||||||
help=Ajuda
|
help=Ajuda
|
||||||
goHomepage=Ir para a Página Inicial
|
goHomepage=Ir para a Página Inicial
|
||||||
joinDiscord=Junte-se ao nosso servidor Discord
|
joinDiscord=Junte-se ao nosso servidor Discord
|
||||||
seeDockerHub=Visite o Docker Hub
|
seeDockerHub=Visite o Docker Hub
|
||||||
visitGithub=Visite o repositório no GitHub
|
visitGithub=Visite o repositŕio no GitHub
|
||||||
donate=Doar
|
donate=Doar
|
||||||
color=Cor
|
color=Cor
|
||||||
sponsor=Patrocinador
|
sponsor=Patrocine
|
||||||
info=Informações
|
info=Informações
|
||||||
pro=Pro
|
pro=Pro
|
||||||
page=Página
|
page=Page
|
||||||
pages=Páginas
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=Política de Privacidade
|
legal.privacy=Política de Privacidade
|
||||||
legal.terms=Termos e Condições
|
legal.terms=Termos e Condições
|
||||||
@@ -90,7 +90,7 @@ legal.impressum=Informações legais
|
|||||||
# Pipeline #
|
# Pipeline #
|
||||||
###############
|
###############
|
||||||
pipeline.header=Menu do Pipeline (Beta)
|
pipeline.header=Menu do Pipeline (Beta)
|
||||||
pipeline.uploadButton=Upload Personalizado
|
pipeline.uploadButton=Upload personalizado
|
||||||
pipeline.configureButton=Configurar
|
pipeline.configureButton=Configurar
|
||||||
pipeline.defaultOption=Personalizado
|
pipeline.defaultOption=Personalizado
|
||||||
pipeline.submitButton=Enviar
|
pipeline.submitButton=Enviar
|
||||||
@@ -103,10 +103,10 @@ pipeline.deletePrompt=Tem certeza de que deseja excluir o pipeline
|
|||||||
######################
|
######################
|
||||||
pipelineOptions.header=Configuração do Pipeline
|
pipelineOptions.header=Configuração do Pipeline
|
||||||
pipelineOptions.pipelineNameLabel=Nome do Pipeline
|
pipelineOptions.pipelineNameLabel=Nome do Pipeline
|
||||||
pipelineOptions.saveSettings=Salvar Configurações da Operação
|
pipelineOptions.saveSettings=Salvar configurações da operação
|
||||||
pipelineOptions.pipelineNamePrompt=Insira o nome do pipeline aqui
|
pipelineOptions.pipelineNamePrompt=Insira o nome do pipeline aqui
|
||||||
pipelineOptions.selectOperation=Selecione uma Operação
|
pipelineOptions.selectOperation=Selecione uma operação
|
||||||
pipelineOptions.addOperationButton=Adicione uma Operação
|
pipelineOptions.addOperationButton=Adicione uma operação
|
||||||
pipelineOptions.pipelineHeader=Pipeline:
|
pipelineOptions.pipelineHeader=Pipeline:
|
||||||
pipelineOptions.saveButton=Baixar
|
pipelineOptions.saveButton=Baixar
|
||||||
pipelineOptions.validateButton=Validar
|
pipelineOptions.validateButton=Validar
|
||||||
@@ -114,21 +114,21 @@ pipelineOptions.validateButton=Validar
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=Atualize para versão Pro
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=Esse recurso só está disponivel para usuários da versão Pro.
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF Pro suporta arquivos de configuração YAML e outros recursos SSO.
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=Procurando por mais recursos de controle de usuários? Veja Stirling PDF Pro
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=Você quer melhorar Stirling PDF?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF possui coleta de dados opcional para ajudar a melhorar o produto. Nós não rastreamos nenhuma informação pessoal ou conteúdo dos arquivos.
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=Por favor considere habilitar coleta de dados para ajudar Stirling-PDF a crescer e nos ajudar a entender nossos usuários melhor.
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=Habilitar coleta de dados
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=Desabilitar coleta de dados
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=Você pode alterar as configurações de coleta de dados no arquivo config/settings.yml
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -162,61 +162,61 @@ settings.zipThreshold=Compactar arquivos quando o número de arquivos baixados e
|
|||||||
settings.signOut=Sair
|
settings.signOut=Sair
|
||||||
settings.accountSettings=Configurações de conta
|
settings.accountSettings=Configurações de conta
|
||||||
settings.bored.help=Habilitar jogos secretos
|
settings.bored.help=Habilitar jogos secretos
|
||||||
settings.cacheInputs.name=Salvar entradas do formulário
|
settings.cacheInputs.name=Salvar entradas de formulário
|
||||||
settings.cacheInputs.help=Habilitar para armazenar entradas usadas anteriormente para execuções futuras
|
settings.cacheInputs.help=Habilitar para armazenar entradas usadas anteriormente para execuções futuras
|
||||||
|
|
||||||
changeCreds.title=Alterar Credenciais
|
changeCreds.title=Alterar credenciais
|
||||||
changeCreds.header=Atualizar Detalhes da Conta
|
changeCreds.header=Atualizar detalhes da sua conta
|
||||||
changeCreds.changePassword=Você está usando as credenciais padrões. Por favor, insira uma nova senha
|
changeCreds.changePassword=Você está usando as credenciais padrões. Por favor, insira uma nova senha
|
||||||
changeCreds.newUsername=Novo Usuário
|
changeCreds.newUsername=Novo usuário
|
||||||
changeCreds.oldPassword=Senha Atual
|
changeCreds.oldPassword=Senha atual
|
||||||
changeCreds.newPassword=Senha Nova
|
changeCreds.newPassword=Nova senha
|
||||||
changeCreds.confirmNewPassword=Confirme a Nova Senha
|
changeCreds.confirmNewPassword=Confirmar nova senha
|
||||||
changeCreds.submit=Enviar Alterações
|
changeCreds.submit=Enviar alterações
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
account.title=Configurações de Conta
|
account.title=Configurações de conta
|
||||||
account.accountSettings=Configurações de Conta
|
account.accountSettings=Configurações de conta
|
||||||
account.adminSettings=Configurações de Administrador – Visualizar e Adicionar Usuários
|
account.adminSettings=Configurações de Administrador – visualizar e adicionar usuários
|
||||||
account.userControlSettings=Configurações de Controle de Usuário
|
account.userControlSettings=Configurações de controle de usuário
|
||||||
account.changeUsername=Alterar Usuário
|
account.changeUsername=Alterar usuário
|
||||||
account.newUsername=Novo Usuário
|
account.newUsername=Novo usuário
|
||||||
account.password=Senha de Confirmação
|
account.password=Senha de confirmação
|
||||||
account.oldPassword=Senha Antiga
|
account.oldPassword=Senha antiga
|
||||||
account.newPassword=Senha Nova
|
account.newPassword=Senha nova
|
||||||
account.changePassword=Alterar a Senha
|
account.changePassword=Alterar a senha
|
||||||
account.confirmNewPassword=Confirme a Nova Senha
|
account.confirmNewPassword=Confirme a nova senha
|
||||||
account.signOut=Sair
|
account.signOut=Sair
|
||||||
account.yourApiKey=Sua chave de API
|
account.yourApiKey=Sua chave de API
|
||||||
account.syncTitle=Sincronize as configurações do navegador com a conta
|
account.syncTitle=Sincronize as configurações do navegador com a conta
|
||||||
account.settingsCompare=Comparação de Configurações:
|
account.settingsCompare=Comparação de configurações:
|
||||||
account.property=Propriedade
|
account.property=Propriedade
|
||||||
account.webBrowserSettings=Configuração do navegador Web
|
account.webBrowserSettings=Configuração do navegador Web
|
||||||
account.syncToBrowser=Sincronizar Conta -> Navegador
|
account.syncToBrowser=Sincronizar conta -> Navegador
|
||||||
account.syncToAccount=Sincronizar Conta <- Navegador
|
account.syncToAccount=Sincronizar conta <- navegador
|
||||||
|
|
||||||
|
|
||||||
adminUserSettings.title=Configurações de controle de usuário
|
adminUserSettings.title=Configurações de controle de usuário
|
||||||
adminUserSettings.header=Configurações de controle do usuário administrador
|
adminUserSettings.header=Configurações de controle de usuário administrador
|
||||||
adminUserSettings.admin=Administrador
|
adminUserSettings.admin=Administrador
|
||||||
adminUserSettings.user=Usuário
|
adminUserSettings.user=Usuário
|
||||||
adminUserSettings.addUser=Adicionar novo usuário
|
adminUserSettings.addUser=Adicionar novo usuário
|
||||||
adminUserSettings.deleteUser=Apagar usuário
|
adminUserSettings.deleteUser=Apagar usuário
|
||||||
adminUserSettings.confirmDeleteUser=O usuário deve ser apagado?
|
adminUserSettings.confirmDeleteUser=O usuário deve ser apagado?
|
||||||
adminUserSettings.confirmChangeUserStatus=O usuário deve ser desabilitado/habilitado?
|
adminUserSettings.confirmChangeUserStatus=O usuário deve ser desabilitado/habilitado?
|
||||||
adminUserSettings.usernameInfo=Nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um e-mail válido.
|
adminUserSettings.usernameInfo=Nome de usuário só pode incluir letras, números e os seguintes caracteres especiais @._+- ou deve ser um email válido.
|
||||||
adminUserSettings.roles=Funções
|
adminUserSettings.roles=Funções
|
||||||
adminUserSettings.role=Função
|
adminUserSettings.role=Função
|
||||||
adminUserSettings.actions=Ações
|
adminUserSettings.actions=Ações
|
||||||
adminUserSettings.apiUser=Usuário de API limitado
|
adminUserSettings.apiUser=Usuário de API limitado
|
||||||
adminUserSettings.extraApiUser=Usuário de API limitado adicional
|
adminUserSettings.extraApiUser=Usuário de API limitado adicional
|
||||||
adminUserSettings.webOnlyUser=Usuário web apenas
|
adminUserSettings.webOnlyUser=Usuário apenas web
|
||||||
adminUserSettings.demoUser=Usuário demo (Sem configurações personalizadas)
|
adminUserSettings.demoUser=Usuário demo (Sem configurações personalizadas)
|
||||||
adminUserSettings.internalApiUser=Usuário interno de API
|
adminUserSettings.internalApiUser=Usuário interno de API
|
||||||
adminUserSettings.forceChange=Forçar usuário a trocar a senha ao iniciar sessão
|
adminUserSettings.forceChange=Forçar usuário a trocar a senha ao iniciar sessão
|
||||||
adminUserSettings.submit=Salvar Usuário
|
adminUserSettings.submit=Salvar usuário
|
||||||
adminUserSettings.changeUserRole=Alterar Função do Usuário
|
adminUserSettings.changeUserRole=Alterar Função de Usuário
|
||||||
adminUserSettings.authenticated=Autenticado
|
adminUserSettings.authenticated=Autenticado
|
||||||
adminUserSettings.editOwnProfil=Editar próprio perfil
|
adminUserSettings.editOwnProfil=Editar próprio perfil
|
||||||
adminUserSettings.enabledUser=usuário habilitado
|
adminUserSettings.enabledUser=usuário habilitado
|
||||||
@@ -235,15 +235,15 @@ database.fileSize=Tamanho do arquivo
|
|||||||
database.deleteBackupFile=Apagar arquivo de backup
|
database.deleteBackupFile=Apagar arquivo de backup
|
||||||
database.importBackupFile=Importar arquivo de backup
|
database.importBackupFile=Importar arquivo de backup
|
||||||
database.downloadBackupFile=Baixar arquivo de backup
|
database.downloadBackupFile=Baixar arquivo de backup
|
||||||
database.info_1=Ao importar dados, é crucial garantir a estrutura correta. Se você não tem certeza do que está fazendo procure auxílio de um profissional. Um erro na estrutura pode ocasionar em mau funcionamento da aplicação, incluindo a impossibilidade da aplicação ser executada.
|
database.info_1=Ao importar dados, é crucial garantir a estrutura correta. Se você não tem certeza do que está fazendo procure auxílio de um profissional. Um erro na estrutura pode ocasionar em mau funcionamento da aplicação, incluindo a possibilidade de perda total da aplicação.
|
||||||
database.info_2=O nome do arquivo não importa ao enviar. Ele será renomeado em seguida para seguir o formato backup_user_yyyyMMddHHmm.sql, garantindo uma convenção de nomes coerente.
|
database.info_2=O nome do arquivo não importa ao enviar. Ele será renomeado em seguida para seguir o formato backup_user_yyyyMMddHHmm.sql, garantindo uma convenção de nomes coerente.
|
||||||
database.submit=Importar Backup
|
database.submit=Importar Backup
|
||||||
database.importIntoDatabaseSuccessed=Importação para o banco de dados bem sucedida
|
database.importIntoDatabaseSuccessed=Importação para o banco de dados bem sucedida
|
||||||
database.fileNotFound=Arquivo não encontrado
|
database.fileNotFound=Arquivo não encontrado
|
||||||
database.fileNullOrEmpty=O arquivo não pode estar nulo ou vazio
|
database.fileNullOrEmpty=O arquivo não estar nulo ou vazio
|
||||||
database.failedImportFile=Falha ao importar arquivo
|
database.failedImportFile=Falha ao importar arquivo
|
||||||
|
|
||||||
session.expired=Sua sessão expirou. Por gentileza atualize a página e tente novamente.
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
@@ -257,11 +257,11 @@ home.viewPdf.desc=Visualizar, anotar, adicionar texto ou imagens
|
|||||||
viewPdf.tags=visualizar,ler,anotar,texto,imagem
|
viewPdf.tags=visualizar,ler,anotar,texto,imagem
|
||||||
|
|
||||||
home.multiTool.title=Multiferramenta de PDF
|
home.multiTool.title=Multiferramenta de PDF
|
||||||
home.multiTool.desc=Mesclar, girar, reorganizar, dividir e remover páginas
|
home.multiTool.desc=Mesclar, girar, reorganizar e remover páginas
|
||||||
multiTool.tags=Multiferramenta, múltiplas operações, Interface do Usuário, Clique e arraste, front-end, lado do cliente, interativo, intratável, movimento, excluir, migrar, dividir
|
multiTool.tags=Multiferramenta,Múltiplas operações,Interface do Usuário,Clique e arraste,front-end,lado do cliente,interativo,intratável,movimento
|
||||||
|
|
||||||
home.merge.title=Mesclar
|
home.merge.title=Mesclar
|
||||||
home.merge.desc=Mescle facilmente vários PDFs em um só.
|
home.merge.desc=Mesclar facilmente vários PDFs em um só.
|
||||||
merge.tags=mesclar,Operações de Página,Back-end,lado do servidor
|
merge.tags=mesclar,Operações de Página,Back-end,lado do servidor
|
||||||
|
|
||||||
home.split.title=Dividir
|
home.split.title=Dividir
|
||||||
@@ -269,7 +269,7 @@ home.split.desc=Dividir PDFs em vários documentos
|
|||||||
split.tags=Operações de Página,dividir,Múltiplas Páginas,cortar,lado do servidor
|
split.tags=Operações de Página,dividir,Múltiplas Páginas,cortar,lado do servidor
|
||||||
|
|
||||||
home.rotate.title=Girar
|
home.rotate.title=Girar
|
||||||
home.rotate.desc=Gire facilmente seus PDFs.
|
home.rotate.desc=Girar facilmente seus PDFs.
|
||||||
rotate.tags=Lado do servidor
|
rotate.tags=Lado do servidor
|
||||||
|
|
||||||
|
|
||||||
@@ -287,11 +287,11 @@ pdfOrganiser.tags=duplex,par,ímpar,ordenar,mover
|
|||||||
|
|
||||||
|
|
||||||
home.addImage.title=Adicionar Imagem
|
home.addImage.title=Adicionar Imagem
|
||||||
home.addImage.desc=Adicionar uma imagem em um local definido no PDF
|
home.addImage.desc=Adicionar uma imagem em um local definido no PDF (Em desenvolvimento)
|
||||||
addImage.tags=img,jpg,imagem,foto
|
addImage.tags=img,jpg,imagem,foto
|
||||||
|
|
||||||
home.watermark.title=Adicionar Marca d'água
|
home.watermark.title="Adicionar Marca d'água"
|
||||||
home.watermark.desc=Adicionar uma marca d'água personalizada ao seu documento PDF.
|
home.watermark.desc="Adicionar uma marca d'água personalizada ao seu documento PDF."
|
||||||
watermark.tags=Texto,repetindo,rótulo,próprio,direitos autorais,marca registrada,img,jpg,imagem,foto
|
watermark.tags=Texto,repetindo,rótulo,próprio,direitos autorais,marca registrada,img,jpg,imagem,foto
|
||||||
|
|
||||||
home.permissions.title=Alterar Permissões
|
home.permissions.title=Alterar Permissões
|
||||||
@@ -303,13 +303,13 @@ home.removePages.title=Remover
|
|||||||
home.removePages.desc=Excluir as páginas indesejadas do seu documento PDF.
|
home.removePages.desc=Excluir as páginas indesejadas do seu documento PDF.
|
||||||
removePages.tags=Remover páginas,excluir páginas
|
removePages.tags=Remover páginas,excluir páginas
|
||||||
|
|
||||||
home.addPassword.title=Adicionar Senha
|
home.addPassword.title=Adicionar senha
|
||||||
home.addPassword.desc=Criptografar seu documento PDF com uma senha.
|
home.addPassword.desc=Criptografar seu documento PDF com uma senha.
|
||||||
addPassword.tags=seguro,segurança
|
addPassword.tags=seguro,segurança
|
||||||
|
|
||||||
home.removePassword.title=Remover Senha
|
home.removePassword.title=Remover Senha
|
||||||
home.removePassword.desc=Remover a proteção por senha do seu documento PDF.
|
home.removePassword.desc=Remover a proteção por senha do seu documento PDF.
|
||||||
removePassword.tags=seguro, descriptografar, segurança, remover senha
|
removePassword.tags=seguro, Descriptografar, segurança, remover senha
|
||||||
|
|
||||||
home.compressPdfs.title=Comprimir
|
home.compressPdfs.title=Comprimir
|
||||||
home.compressPdfs.desc=Comprimir PDFs para reduzir o tamanho do arquivo.
|
home.compressPdfs.desc=Comprimir PDFs para reduzir o tamanho do arquivo.
|
||||||
@@ -339,7 +339,7 @@ pdfToPDFA.tags=arquivo,longo prazo,padrão,conversão,armazenamento,preservaçã
|
|||||||
|
|
||||||
home.PDFToWord.title=PDF para Word
|
home.PDFToWord.title=PDF para Word
|
||||||
home.PDFToWord.desc=Converter PDF para formatos Word (DOC, DOCX e ODT)
|
home.PDFToWord.desc=Converter PDF para formatos Word (DOC, DOCX e ODT)
|
||||||
PDFToWord.tags=doc,docx,odt,word,transformação,formato,conversão,escritório,microsoft,doc
|
PDFToWord.tags=doc,docx,odt,word,transformação,formato,conversão,escritório,microsoft,arquivo doc
|
||||||
|
|
||||||
home.PDFToPresentation.title=PDF para Apresentação
|
home.PDFToPresentation.title=PDF para Apresentação
|
||||||
home.PDFToPresentation.desc=Converter PDF para formatos de apresentação (PPT, PPTX e ODP)
|
home.PDFToPresentation.desc=Converter PDF para formatos de apresentação (PPT, PPTX e ODP)
|
||||||
@@ -355,12 +355,12 @@ PDFToHTML.tags=conteúdo web,compatível com navegador
|
|||||||
|
|
||||||
|
|
||||||
home.PDFToXML.title=PDF para XML
|
home.PDFToXML.title=PDF para XML
|
||||||
home.PDFToXML.desc=Converter PDF para formato XML
|
home.PDFToXML.desc=Converter PDF para o formato XML
|
||||||
PDFToXML.tags=extração-de-dados,conteúdo-estruturado,interoperabilidade,transformação,converter
|
PDFToXML.tags=extração-de-dados,conteúdo-estruturado,interoperabilidade,transformação,converter
|
||||||
|
|
||||||
home.ScannerImageSplit.title=Detectar/Dividir Fotos Digitalizadas
|
home.ScannerImageSplit.title=Detectar/Dividir Fotos Digitalizadas
|
||||||
home.ScannerImageSplit.desc=Divide várias fotos de dentro de uma imagem/PDF
|
home.ScannerImageSplit.desc=Divide várias fotos de dentro de uma imagem/PDF digitalizado
|
||||||
ScannerImageSplit.tags=separar,detecção-automática,digitalizações,fotos-múltiplas,organizar
|
ScannerImageSplit.tags=separar,detecção-automática,digitalizações,foto-múltipla,organizar
|
||||||
|
|
||||||
home.sign.title=Assinar
|
home.sign.title=Assinar
|
||||||
home.sign.desc=Adicionar assinatura ao PDF por desenho, texto ou imagem
|
home.sign.desc=Adicionar assinatura ao PDF por desenho, texto ou imagem
|
||||||
@@ -378,7 +378,7 @@ home.removeBlanks.title=Remover Páginas em Branco
|
|||||||
home.removeBlanks.desc=Detectar e remover páginas em branco de um documento
|
home.removeBlanks.desc=Detectar e remover páginas em branco de um documento
|
||||||
removeBlanks.tags=limpeza,otimização,sem-conteúdo,organizar
|
removeBlanks.tags=limpeza,otimização,sem-conteúdo,organizar
|
||||||
|
|
||||||
home.removeAnnotations.title=Remover Anotações
|
home.removeAnnotations.title=Remover anotações
|
||||||
home.removeAnnotations.desc=Remove todos os comentários/anotações de um PDF
|
home.removeAnnotations.desc=Remove todos os comentários/anotações de um PDF
|
||||||
removeAnnotations.tags=comentários,destaque,notas,marcação,remover
|
removeAnnotations.tags=comentários,destaque,notas,marcação,remover
|
||||||
|
|
||||||
@@ -426,7 +426,7 @@ home.autoSplitPDF.title=Divisão Automática de Páginas
|
|||||||
home.autoSplitPDF.desc=Dividir automaticamente um PDF digitalizado com separador de páginas físicas QR Code
|
home.autoSplitPDF.desc=Dividir automaticamente um PDF digitalizado com separador de páginas físicas QR Code
|
||||||
autoSplitPDF.tags=baseado-em-QR,separar,segmento-de-digitalização,organizar
|
autoSplitPDF.tags=baseado-em-QR,separar,segmento-de-digitalização,organizar
|
||||||
|
|
||||||
home.sanitizePdf.title=Higienizar
|
home.sanitizePdf.title=Sanitizar
|
||||||
home.sanitizePdf.desc=Remover scripts e outros elementos de arquivos PDF
|
home.sanitizePdf.desc=Remover scripts e outros elementos de arquivos PDF
|
||||||
sanitizePdf.tags=limpar,seguro,protegido,remover-ameaças
|
sanitizePdf.tags=limpar,seguro,protegido,remover-ameaças
|
||||||
|
|
||||||
@@ -468,7 +468,7 @@ home.autoRedact.desc=Ocultação automática (escurecimento) de texto em um PDF
|
|||||||
autoRedact.tags=Redigir,ocultar,escurecer,preto,marcador,oculto
|
autoRedact.tags=Redigir,ocultar,escurecer,preto,marcador,oculto
|
||||||
|
|
||||||
home.tableExtraxt.title=PDF para CSV
|
home.tableExtraxt.title=PDF para CSV
|
||||||
home.tableExtraxt.desc=Extrai tabelas de um PDF convertendo para CSV
|
home.tableExtraxt.desc=Extrai tabelas de um PDF convertendo-o para CSV
|
||||||
tableExtraxt.tags=CSV,extração de tabela,extrair,converter
|
tableExtraxt.tags=CSV,extração de tabela,extrair,converter
|
||||||
|
|
||||||
|
|
||||||
@@ -481,13 +481,13 @@ home.overlay-pdfs.title=Sobrepor PDFs
|
|||||||
home.overlay-pdfs.desc=Sobrepõe PDFs sobre outro PDF
|
home.overlay-pdfs.desc=Sobrepõe PDFs sobre outro PDF
|
||||||
overlay-pdfs.tags=Sobreposição
|
overlay-pdfs.tags=Sobreposição
|
||||||
|
|
||||||
home.split-by-sections.title=Dividir PDF por Seções
|
home.split-by-sections.title=Dividir PDF por seções
|
||||||
home.split-by-sections.desc=Divida cada página de um PDF em seções horizontais e verticais menores
|
home.split-by-sections.desc=Divida cada página de um PDF em seções horizontais e verticais menores
|
||||||
split-by-sections.tags=Seção Dividir, Dividir, Personalizar
|
split-by-sections.tags=Seção Dividir, Dividir, Personalizar
|
||||||
|
|
||||||
home.AddStampRequest.title=Adicionar Carimbo ao PDF
|
home.AddStampRequest.title=Adicionar carimbo ao PDF
|
||||||
home.AddStampRequest.desc=Adicione texto ou carimbos de imagem em locais definidos
|
home.AddStampRequest.desc=Adicione texto ou carimbos de imagem em locais definidos
|
||||||
AddStampRequest.tags=Carimbo,Adicionar imagem,centralizar imagem,Marca d'água,PDF,Incorporar,Personalizar
|
AddStampRequest.tags="Carimbo,Adicionar imagem,centralizar imagem,Marca d'água,PDF,Incorporar,Personalizar"
|
||||||
|
|
||||||
|
|
||||||
home.PDFToBook.title=PDF para Livro
|
home.PDFToBook.title=PDF para Livro
|
||||||
@@ -495,36 +495,36 @@ home.PDFToBook.desc=Converte PDF para formatos de livro/quadrinhos usando Calibr
|
|||||||
PDFToBook.tags=Livro,Quadrinhos,Calibre,Converter,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
PDFToBook.tags=Livro,Quadrinhos,Calibre,Converter,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||||
|
|
||||||
home.BookToPDF.title=Livro para PDF
|
home.BookToPDF.title=Livro para PDF
|
||||||
home.BookToPDF.desc=Converte formatos de livros/quadrinhos para PDF usando Calibre
|
home.BookToPDF.desc=Converte formatos de livros/quadrinhos em PDF usando Calibre
|
||||||
BookToPDF.tags=Livro,Quadrinhos,Calibre,Converter,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
BookToPDF.tags=Livro,Quadrinhos,Calibre,Converter,manga,amazon,kindle,epub,mobi,azw3,docx,rtf,txt,html,lit,fb2,pdb,lrf
|
||||||
|
|
||||||
home.removeImagePdf.title=Remover Imagem
|
home.removeImagePdf.title=Remover imagem
|
||||||
home.removeImagePdf.desc=Remova a imagem do PDF para reduzir o tamanho do arquivo
|
home.removeImagePdf.desc=Remova a imagem do PDF para reduzir o tamanho do arquivo
|
||||||
removeImagePdf.tags=Remover imagem,operações de página,back-end,lado do servidor
|
removeImagePdf.tags=Remover imagem,operações de página,back-end,lado do servidor
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=Divide PDF por Capítulos
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=Divide um PDF em vários arquivos baseado na sua estrutura de capítulos.
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=dividir,capítulos,favoritos,organizar
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=Substituir-Inverter-Cor
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=Substitui-Inverter Cor PDF
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=Substitui e Inverte Cor
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=Substitui cor de um texto e plano de fundo de um PDF e inverte a toda cor do PDF para reduzir o tamanho
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=Substitui Cor, Operações na Página, back end, lado do servidor
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=Substituir ou inverter cor Opções
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=Padrão(Padrão cores de alto constraste)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=Customizado(Cores customizadas)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=Inversão Completa(Inverte todas cores)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=Opções de cores de alto contraste
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=Texto branco em um plano de fundo preto
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=Texto preto em um plano de fundo branco
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=Texto amarelo em um plano de fundo preto
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=Texto verde em um plano de fundo preto
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=Escolha cor do texto
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=Escolha cor do plano de fundo
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=Substituir
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -541,23 +541,24 @@ login.rememberme=Lembrar de mim
|
|||||||
login.invalid=Usuário ou senha inválidos.
|
login.invalid=Usuário ou senha inválidos.
|
||||||
login.locked=Sua conta foi bloqueada.
|
login.locked=Sua conta foi bloqueada.
|
||||||
login.signinTitle=Por favor, inicie a sessão
|
login.signinTitle=Por favor, inicie a sessão
|
||||||
login.ssoSignIn=Iniciar sessão através de login único (SSO)
|
login.ssoSignIn=Iniciar sessão através de início de sessão único
|
||||||
login.oauth2AutoCreateDisabled=Auto-Criar Usuário OAUTH2 Desativado
|
login.oauth2AutoCreateDisabled=Auto-Criar Usuário OAUTH2 Desativado
|
||||||
login.oauth2AdminBlockedUser=O registro ou login de usuários não registrados está atualmente bloqueado. Entre em contato com o administrador.
|
login.oauth2AdminBlockedUser=O registro ou login de usuários não registrados está atualmente bloqueado. Entre em contato com o administrador.
|
||||||
login.oauth2RequestNotFound=Solicitação de autorização não encontrada
|
login.oauth2RequestNotFound=Solicitação de autorização não encontrada
|
||||||
login.oauth2InvalidUserInfoResponse=Resposta de informação de usuário inválida
|
login.oauth2InvalidUserInfoResponse=Resposta de informação de usuário inválida
|
||||||
login.oauth2invalidRequest=Requisição Inválida
|
login.oauth2invalidRequest=Requisição inválida
|
||||||
login.oauth2AccessDenied=Acesso Negado
|
login.oauth2AccessDenied=Acesso negado
|
||||||
login.oauth2InvalidTokenResponse=Resposta de Token Inválida
|
login.oauth2InvalidTokenResponse=Resposta de token inválida
|
||||||
login.oauth2InvalidIdToken=Id de Token Inválido
|
login.oauth2InvalidIdToken=Id de token inválido
|
||||||
login.userIsDisabled=O usuário está desativado, o login está atualmente bloqueado com este nome de usuário. Entre em contato com o administrador.
|
login.userIsDisabled=O usuário está desativado, o login está atualmente bloqueado com este nome de usuário. Entre em contato com o administrador.
|
||||||
login.alreadyLoggedIn=Você já está conectado
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=aparelhos. Por favor saia dos aparelhos e tente novamente.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=Você tem muitas sessões ativas
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Redação Automática de Dados
|
autoRedact.title=Redigir automaticamente
|
||||||
autoRedact.header=Redação Automática de Dados
|
autoRedact.header=Redigir automaticamente
|
||||||
autoRedact.colorLabel=Cor
|
autoRedact.colorLabel=Cor
|
||||||
autoRedact.textsToRedactLabel=Texto para redigir (separado por linha)
|
autoRedact.textsToRedactLabel=Texto para redigir (separado por linha)
|
||||||
autoRedact.textsToRedactPlaceholder=por exemplo: \nConfidencial \nSecreto
|
autoRedact.textsToRedactPlaceholder=por exemplo: \nConfidencial \nSecreto
|
||||||
@@ -643,22 +644,22 @@ AddStampRequest.fontSize=Tamanho da fonte/imagem
|
|||||||
AddStampRequest.rotation=Rotação
|
AddStampRequest.rotation=Rotação
|
||||||
AddStampRequest.opacity=Opacidade
|
AddStampRequest.opacity=Opacidade
|
||||||
AddStampRequest.position=Posição
|
AddStampRequest.position=Posição
|
||||||
AddStampRequest.overrideX=Substituir Coordenada X
|
AddStampRequest.overrideX=Substituir coordenada X
|
||||||
AddStampRequest.overrideY=Substituir Coordenada Y
|
AddStampRequest.overrideY=Substituir coordenada Y
|
||||||
AddStampRequest.customMargin=Margem personalizada
|
AddStampRequest.customMargin=Margem personalizada
|
||||||
AddStampRequest.customColor=Cor de texto personalizada
|
AddStampRequest.customColor=Cor de texto personalizada
|
||||||
AddStampRequest.submit=Enviar
|
AddStampRequest.submit=Enviar
|
||||||
|
|
||||||
|
|
||||||
#sanitizePDF
|
#sanitizePDF
|
||||||
sanitizePDF.title=Higienizar PDF
|
sanitizePDF.title=Sanitizar PDF
|
||||||
sanitizePDF.header=Higienizar um arquivo PDF
|
sanitizePDF.header=Sanitizar um arquivo PDF
|
||||||
sanitizePDF.selectText.1=Remover ações de JavaScript
|
sanitizePDF.selectText.1=Remover ações de JavaScript
|
||||||
sanitizePDF.selectText.2=Remover arquivos embutidos
|
sanitizePDF.selectText.2=Remover arquivos embutidos
|
||||||
sanitizePDF.selectText.3=Remover metadados
|
sanitizePDF.selectText.3=Remover metadados
|
||||||
sanitizePDF.selectText.4=Remover links
|
sanitizePDF.selectText.4=Remover links
|
||||||
sanitizePDF.selectText.5=Remover fontes
|
sanitizePDF.selectText.5=Remover fontes
|
||||||
sanitizePDF.submit=Higienizar PDF
|
sanitizePDF.submit=Sanitizar PDF
|
||||||
|
|
||||||
|
|
||||||
#addPageNumbers
|
#addPageNumbers
|
||||||
@@ -738,9 +739,9 @@ certSign.title=Assinatura com Certificado
|
|||||||
certSign.header=Assine um PDF com o seu certificado (Em desenvolvimento)
|
certSign.header=Assine um PDF com o seu certificado (Em desenvolvimento)
|
||||||
certSign.selectPDF=Selecione um arquivo PDF para assinatura:
|
certSign.selectPDF=Selecione um arquivo PDF para assinatura:
|
||||||
certSign.jksNote=Nota: Se o seu tipo de certificado não estiver listado abaixo, converta-o em um arquivo Java Keystore (.jks) usando a ferramenta de linha de comando keytool. Em seguida, escolha a opção de arquivo .jks abaixo.
|
certSign.jksNote=Nota: Se o seu tipo de certificado não estiver listado abaixo, converta-o em um arquivo Java Keystore (.jks) usando a ferramenta de linha de comando keytool. Em seguida, escolha a opção de arquivo .jks abaixo.
|
||||||
certSign.selectKey=Selecione o seu arquivo de chave privada (formato PKCS#8, pode ser .pem ou .der):
|
certSign.selectKey="Selecione o seu arquivo de chave privada (formato PKCS#8, pode ser .pem ou .der):"
|
||||||
certSign.selectCert=Selecione o seu arquivo de certificado (formato X.509, pode ser .pem ou .der):
|
certSign.selectCert=Selecione o seu arquivo de certificado (formato X.509, pode ser .pem ou .der):
|
||||||
certSign.selectP12=Selecione o seu arquivo de armazenamento de chave PKCS#12 (.p12 ou .pfx) (opcional, se fornecido, deve conter a sua chave privada e certificado):
|
certSign.selectP12="Selecione o seu arquivo de armazenamento de chave PKCS#12 (.p12 ou .pfx) (opcional, se fornecido, deve conter a sua chave privada e certificado):"
|
||||||
certSign.selectJKS=Selecione seu arquivo Java Keystore (.jks ou .keystore):
|
certSign.selectJKS=Selecione seu arquivo Java Keystore (.jks ou .keystore):
|
||||||
certSign.certType=Tipo de Certificado
|
certSign.certType=Tipo de Certificado
|
||||||
certSign.password=Digite a senha do seu armazenamento de chave ou chave privada (se aplicável):
|
certSign.password=Digite a senha do seu armazenamento de chave ou chave privada (se aplicável):
|
||||||
@@ -752,7 +753,7 @@ certSign.submit=Assinar PDF
|
|||||||
|
|
||||||
|
|
||||||
#removeCertSign
|
#removeCertSign
|
||||||
removeCertSign.title=Remover Assinatura do Certificado
|
removeCertSign.title=Remover assinatura com Certificado
|
||||||
removeCertSign.header=Remover o certificado digital do PDF
|
removeCertSign.header=Remover o certificado digital do PDF
|
||||||
removeCertSign.selectPDF=Selecione um arquivo PDF:
|
removeCertSign.selectPDF=Selecione um arquivo PDF:
|
||||||
removeCertSign.submit=Remover assinatura
|
removeCertSign.submit=Remover assinatura
|
||||||
@@ -764,13 +765,13 @@ removeBlanks.header=Remover páginas em branco
|
|||||||
removeBlanks.threshold=Limite de brancura de pixel:
|
removeBlanks.threshold=Limite de brancura de pixel:
|
||||||
removeBlanks.thresholdDesc=Limite para determinar o quão branco um pixel branco deve ser para ser classificado como 'Branco'. 0 = Preto, 255 branco puro.
|
removeBlanks.thresholdDesc=Limite para determinar o quão branco um pixel branco deve ser para ser classificado como 'Branco'. 0 = Preto, 255 branco puro.
|
||||||
removeBlanks.whitePercent=Porcentagem de Branco (%):
|
removeBlanks.whitePercent=Porcentagem de Branco (%):
|
||||||
removeBlanks.whitePercentDesc=Porcentagem da página que devem ter pixels “brancos” para serem removidas
|
removeBlanks.whitePercentDesc=Porcentagem de páginas que devem ter pixels “brancos” para serem removidas
|
||||||
removeBlanks.submit=Remover páginas em branco
|
removeBlanks.submit=Remover páginas em branco
|
||||||
|
|
||||||
|
|
||||||
#removeAnnotations
|
#removeAnnotations
|
||||||
removeAnnotations.title=Remover Anotações
|
removeAnnotations.title=Remover anotações
|
||||||
removeAnnotations.header=Remover Anotações
|
removeAnnotations.header=Remover anotações
|
||||||
removeAnnotations.submit=Remover
|
removeAnnotations.submit=Remover
|
||||||
|
|
||||||
|
|
||||||
@@ -836,26 +837,26 @@ ScannerImageSplit.info=Python não está instalado. É necessário para executar
|
|||||||
#OCR
|
#OCR
|
||||||
ocr.title=OCR / Limpeza de Digitalização
|
ocr.title=OCR / Limpeza de Digitalização
|
||||||
ocr.header=OCR / Limpeza de Digitalização (Reconhecimento Óptico de Caracteres)
|
ocr.header=OCR / Limpeza de Digitalização (Reconhecimento Óptico de Caracteres)
|
||||||
ocr.selectText.1=Selecione os idiomas a serem detectados no PDF (os listados são os atualmente instalados):
|
ocr.selectText.1=Selecione os idiomas a serem detectados no PDF (os listados são os atualmente detectados):
|
||||||
ocr.selectText.2=Criar um arquivo de texto contendo o texto OCR junto do PDF com OCR
|
ocr.selectText.2=Criar um arquivo de texto contendo o texto OCR ao lado do PDF com OCR
|
||||||
ocr.selectText.3=Páginas corretamente digitalizadas em um ângulo inclinado, gire-as de volta à posição original
|
ocr.selectText.3=Páginas corretamente digitalizadas em um ângulo inclinado, gire-as de volta à posição original
|
||||||
ocr.selectText.4=Limpar a página para reduzir a probabilidade de o OCR encontrar texto no ruído de fundo (sem alteração na saída)
|
ocr.selectText.4=Limpar a página para reduzir a probabilidade de o OCR encontrar texto no ruído de fundo (sem alteração na saída)
|
||||||
ocr.selectText.5=Limpar a página para reduzir a probabilidade de o OCR encontrar texto no ruído de fundo, mantendo a limpeza na saída.
|
ocr.selectText.5=Limpar a página para reduzir a probabilidade de o OCR encontrar texto no ruído de fundo, mantendo a limpeza na saída.
|
||||||
ocr.selectText.6=Ignorar páginas com texto interativo, processar por OCR apenas as páginas com imagens
|
ocr.selectText.6=Ignorar páginas com texto interativo, processar apenas as páginas de OCR que são imagens
|
||||||
ocr.selectText.7=Forçar OCR, executar OCR em todas as páginas, removendo todos os elementos de texto originais
|
ocr.selectText.7=Forçar OCR, executar OCR em todas as páginas, removendo todos os elementos de texto originais
|
||||||
ocr.selectText.8=Normal (gerará um erro se o PDF já contiver texto)
|
ocr.selectText.8=Normal (gerará um erro se o PDF já contiver texto)
|
||||||
ocr.selectText.9=Configurações Adicionais
|
ocr.selectText.9=Configurações adicionais
|
||||||
ocr.selectText.10=Modo OCR
|
ocr.selectText.10=Modo OCR
|
||||||
ocr.selectText.11=Remover imagens após o OCR (remove TODAS as imagens, útil apenas como parte do processo de conversão)
|
ocr.selectText.11=Remover imagens após o OCR (remove TODAS as imagens, útil apenas como parte do processo de conversão)
|
||||||
ocr.selectText.12=Tipo de Renderização (avançado)
|
ocr.selectText.12=Tipo de renderização (avançado)
|
||||||
ocr.help=Por favor, leia a documentação sobre como usar isso para outros idiomas e/ou fora do ambiente Docker
|
ocr.help=Por favor, leia a documentação sobre como usar isso para outros idiomas e/ou fora do ambiente Docker
|
||||||
ocr.credit=Este serviço usa OCRmyPDF e Tesseract para OCR.
|
ocr.credit=Este serviço usa OCRmyPDF e Tesseract para OCR.
|
||||||
ocr.submit=Processar PDF com OCR
|
ocr.submit=Processar PDF com OCR
|
||||||
|
|
||||||
|
|
||||||
#extractImages
|
#extractImages
|
||||||
extractImages.title=Extrair Imagens
|
extractImages.title=Extrair imagens
|
||||||
extractImages.header=Extrair Imagens
|
extractImages.header=Extrair imagens
|
||||||
extractImages.selectText=Selecione o formato de imagem para converter as imagens extraídas
|
extractImages.selectText=Selecione o formato de imagem para converter as imagens extraídas
|
||||||
extractImages.allowDuplicates=Salvar imagens duplicadas
|
extractImages.allowDuplicates=Salvar imagens duplicadas
|
||||||
extractImages.submit=Extrair
|
extractImages.submit=Extrair
|
||||||
@@ -883,7 +884,7 @@ compress.submit=Comprimir
|
|||||||
|
|
||||||
|
|
||||||
#Add image
|
#Add image
|
||||||
addImage.title=Adicionar Imagem
|
addImage.title=Adicionar imagem
|
||||||
addImage.header=Adicionar imagem ao PDF
|
addImage.header=Adicionar imagem ao PDF
|
||||||
addImage.everyPage=Para cada página?
|
addImage.everyPage=Para cada página?
|
||||||
addImage.upload=Enviar imagem
|
addImage.upload=Enviar imagem
|
||||||
@@ -892,7 +893,7 @@ addImage.submit=Adicionar imagem
|
|||||||
|
|
||||||
#merge
|
#merge
|
||||||
merge.title=Mesclar
|
merge.title=Mesclar
|
||||||
merge.header=Mesclar vários PDFs (2+)
|
merge.header=Mesclar Vários PDFs (2+)
|
||||||
merge.sortByName=Classificar por nome
|
merge.sortByName=Classificar por nome
|
||||||
merge.sortByDate=Classificar por data
|
merge.sortByDate=Classificar por data
|
||||||
merge.removeCertSign=Remover a assinatura digital do arquivo mesclado?
|
merge.removeCertSign=Remover a assinatura digital do arquivo mesclado?
|
||||||
@@ -906,7 +907,7 @@ pdfOrganiser.submit=Reorganizar páginas
|
|||||||
pdfOrganiser.mode=Modo
|
pdfOrganiser.mode=Modo
|
||||||
pdfOrganiser.mode.1=Ordem de página personalizada
|
pdfOrganiser.mode.1=Ordem de página personalizada
|
||||||
pdfOrganiser.mode.2=Ordem inversa
|
pdfOrganiser.mode.2=Ordem inversa
|
||||||
pdfOrganiser.mode.3=Classificação duplex
|
pdfOrganiser.mode.3=Classificação Duplex
|
||||||
pdfOrganiser.mode.4=Classificação de livreto
|
pdfOrganiser.mode.4=Classificação de livreto
|
||||||
pdfOrganiser.mode.5=Classificação de livreto com ponto lateral
|
pdfOrganiser.mode.5=Classificação de livreto com ponto lateral
|
||||||
pdfOrganiser.mode.6=Divisão ímpar-par
|
pdfOrganiser.mode.6=Divisão ímpar-par
|
||||||
@@ -971,7 +972,7 @@ imageToPDF.selectText.5=Converter em PDFs separados
|
|||||||
|
|
||||||
|
|
||||||
#pdfToImage
|
#pdfToImage
|
||||||
pdfToImage.title=PDF para Imagem
|
pdfToImage.title=PDF para imagem
|
||||||
pdfToImage.header=Converter PDF para imagem
|
pdfToImage.header=Converter PDF para imagem
|
||||||
pdfToImage.selectText=Formato de imagem
|
pdfToImage.selectText=Formato de imagem
|
||||||
pdfToImage.singleOrMultiple=Tipo de resultado de imagem
|
pdfToImage.singleOrMultiple=Tipo de resultado de imagem
|
||||||
@@ -980,7 +981,7 @@ pdfToImage.multi=Várias imagens, uma imagem por página
|
|||||||
pdfToImage.colorType=Tipo de cor
|
pdfToImage.colorType=Tipo de cor
|
||||||
pdfToImage.color=Colorida
|
pdfToImage.color=Colorida
|
||||||
pdfToImage.grey=Escala de Cinza
|
pdfToImage.grey=Escala de Cinza
|
||||||
pdfToImage.blackwhite=Preto e Branco (pode perder informações!)
|
pdfToImage.blackwhite=Preto e Branco (pode perder de dados!)
|
||||||
pdfToImage.submit=Converter
|
pdfToImage.submit=Converter
|
||||||
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
pdfToImage.info=Python não está instalado. Necessário para conversão WebP.
|
||||||
|
|
||||||
@@ -991,7 +992,7 @@ addPassword.header=Adicionar Senha (Criptografar)
|
|||||||
addPassword.selectText.1=Selecione o PDF para Criptografar
|
addPassword.selectText.1=Selecione o PDF para Criptografar
|
||||||
addPassword.selectText.2=Senha
|
addPassword.selectText.2=Senha
|
||||||
addPassword.selectText.3=Tamanho da Chave de Criptografia
|
addPassword.selectText.3=Tamanho da Chave de Criptografia
|
||||||
addPassword.selectText.4=Valores mais altos são mais seguros, mas valores mais baixos são melhores para compatibilidade.
|
addPassword.selectText.4=Valores mais altos são mais seguros, mas valores mais baixos são mais compatíveis.
|
||||||
addPassword.selectText.5=Permissões a serem definidas (recomendado para uso junto com a senha do proprietário)
|
addPassword.selectText.5=Permissões a serem definidas (recomendado para uso junto com a senha do proprietário)
|
||||||
addPassword.selectText.6=Impedir a montagem do documento
|
addPassword.selectText.6=Impedir a montagem do documento
|
||||||
addPassword.selectText.7=Impedir a extração de conteúdo
|
addPassword.selectText.7=Impedir a extração de conteúdo
|
||||||
@@ -1008,26 +1009,26 @@ addPassword.submit=Criptografar
|
|||||||
|
|
||||||
|
|
||||||
#watermark
|
#watermark
|
||||||
watermark.title=Adicionar marca d'água
|
watermark.title="Adicionar marca d'água"
|
||||||
watermark.header=Adicionar marca d'água
|
watermark.header="Adicionar marca d'água"
|
||||||
watermark.selectText.1=Selecione PDF para adicionar a marca d'água:
|
watermark.selectText.1="Selecione PDF para adicionar a marca d'água:"
|
||||||
watermark.selectText.2=Texto da marca d'água:
|
watermark.selectText.2="Texto da marca d'água:"
|
||||||
watermark.selectText.3=Tamanho da fonte:
|
watermark.selectText.3=Tamanho da fonte:
|
||||||
watermark.selectText.4=Rotação (0-360):
|
watermark.selectText.4=Rotação (0-360):
|
||||||
watermark.selectText.5=widthSpacer (Espaço entre cada marca d'água horizontalmente):
|
watermark.selectText.5="widthSpacer (Espaço entre cada marca d'água horizontalmente):"
|
||||||
watermark.selectText.6=heightSpacer (Espaço entre cada marca d'água verticalmente):
|
watermark.selectText.6="heightSpacer (Espaço entre cada marca d'água verticalmente):"
|
||||||
watermark.selectText.7=Opacidade (0% - 100%):
|
watermark.selectText.7=Opacidade (0% - 100%):
|
||||||
watermark.selectText.8=Tipo de marca d'água:
|
watermark.selectText.8="Tipo de marca d'água:"
|
||||||
watermark.selectText.9=Imagem da marca d'água:
|
watermark.selectText.9="Imagem da marca d'água:"
|
||||||
watermark.selectText.10=Converter PDF em imagem PDF
|
watermark.selectText.10=Converter PDF em imagem PDF
|
||||||
watermark.submit=Adicionar marca d'água
|
watermark.submit="Adicionar marca d'água"
|
||||||
watermark.type.1=Texto
|
watermark.type.1=Texto
|
||||||
watermark.type.2=Imagem
|
watermark.type.2=Imagem
|
||||||
|
|
||||||
|
|
||||||
#Change permissions
|
#Change permissions
|
||||||
permissions.title=Alterar Permissões
|
permissions.title=Alterar permissões
|
||||||
permissions.header=Alterar Permissões
|
permissions.header=Alterar permissões
|
||||||
permissions.warning=Aviso: para que essas permissões sejam imutáveis, é recomendável defini-las com uma senha através da página Adicionar Senha
|
permissions.warning=Aviso: para que essas permissões sejam imutáveis, é recomendável defini-las com uma senha através da página Adicionar Senha
|
||||||
permissions.selectText.1=Selecione o PDF para alterar as permissões
|
permissions.selectText.1=Selecione o PDF para alterar as permissões
|
||||||
permissions.selectText.2=Permissões para definir
|
permissions.selectText.2=Permissões para definir
|
||||||
@@ -1043,7 +1044,7 @@ permissions.submit=Alterar
|
|||||||
|
|
||||||
|
|
||||||
#remove password
|
#remove password
|
||||||
removePassword.title=Remover Senha
|
removePassword.title=Remover senha
|
||||||
removePassword.header=Remover senha (descriptografar)
|
removePassword.header=Remover senha (descriptografar)
|
||||||
removePassword.selectText.1=Selecione o PDF para descriptografar
|
removePassword.selectText.1=Selecione o PDF para descriptografar
|
||||||
removePassword.selectText.2=Senha
|
removePassword.selectText.2=Senha
|
||||||
@@ -1051,7 +1052,7 @@ removePassword.submit=Remover
|
|||||||
|
|
||||||
|
|
||||||
#changeMetadata
|
#changeMetadata
|
||||||
changeMetadata.title=Alterar Metadados
|
changeMetadata.title=Alterar metadados
|
||||||
changeMetadata.header=Alterar metadados
|
changeMetadata.header=Alterar metadados
|
||||||
changeMetadata.selectText.1=Edite as variáveis que deseja alterar
|
changeMetadata.selectText.1=Edite as variáveis que deseja alterar
|
||||||
changeMetadata.selectText.2=Excluir todos os metadados
|
changeMetadata.selectText.2=Excluir todos os metadados
|
||||||
@@ -1135,7 +1136,7 @@ split-by-size-or-count.submit=Enviar
|
|||||||
|
|
||||||
|
|
||||||
#overlay-pdfs
|
#overlay-pdfs
|
||||||
overlay-pdfs.header=Sobrepor Arquivos PDF
|
overlay-pdfs.header=Sobrepor arquivos PDF
|
||||||
overlay-pdfs.baseFile.label=Selecione o arquivo PDF base
|
overlay-pdfs.baseFile.label=Selecione o arquivo PDF base
|
||||||
overlay-pdfs.overlayFiles.label=Selecione os arquivos PDF para sobreposição
|
overlay-pdfs.overlayFiles.label=Selecione os arquivos PDF para sobreposição
|
||||||
overlay-pdfs.mode.label=Selecione o modo de sobreposição
|
overlay-pdfs.mode.label=Selecione o modo de sobreposição
|
||||||
@@ -1153,7 +1154,7 @@ overlay-pdfs.submit=Enviar
|
|||||||
#split-by-sections
|
#split-by-sections
|
||||||
split-by-sections.title=Dividir PDF por seções
|
split-by-sections.title=Dividir PDF por seções
|
||||||
split-by-sections.header=Divida o PDF em seções
|
split-by-sections.header=Divida o PDF em seções
|
||||||
split-by-sections.horizontal.label=Divisões Horizontais
|
split-by-sections.horizontal.label=Divisões horizontais
|
||||||
split-by-sections.vertical.label=Divisões Verticais
|
split-by-sections.vertical.label=Divisões Verticais
|
||||||
split-by-sections.horizontal.placeholder=Insira o número de divisões horizontais
|
split-by-sections.horizontal.placeholder=Insira o número de divisões horizontais
|
||||||
split-by-sections.vertical.placeholder=Insira o número de divisões verticais
|
split-by-sections.vertical.placeholder=Insira o número de divisões verticais
|
||||||
@@ -1171,8 +1172,8 @@ printFile.submit=Imprimir
|
|||||||
|
|
||||||
#licenses
|
#licenses
|
||||||
licenses.nav=Licenças
|
licenses.nav=Licenças
|
||||||
licenses.title=Licenças de Terceiros
|
licenses.title=Licenças de terceiros
|
||||||
licenses.header=Licenças de Terceiros
|
licenses.header=Licenças de terceiros
|
||||||
licenses.module=Módulo
|
licenses.module=Módulo
|
||||||
licenses.version=Versão
|
licenses.version=Versão
|
||||||
licenses.license=Licença
|
licenses.license=Licença
|
||||||
@@ -1181,10 +1182,10 @@ licenses.license=Licença
|
|||||||
survey.nav=Pesquisa
|
survey.nav=Pesquisa
|
||||||
survey.title=Pesquisa Stirling-PDF
|
survey.title=Pesquisa Stirling-PDF
|
||||||
survey.description=Stirling-PDF não tem rastreamento, então queremos ouvir nossos usuários para melhorar o Stirling-PDF!
|
survey.description=Stirling-PDF não tem rastreamento, então queremos ouvir nossos usuários para melhorar o Stirling-PDF!
|
||||||
survey.changes=Stirling-PDF mudou desde o a última pesquisa! Para saber mais acesse nosso post no blog:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=Com essas mudanças estamos implementando suporte empresarial pago e financeamento
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=Por favor, considere responder à nossa pesquisa!
|
survey.please=Por favor, considere responder à nossa pesquisa!
|
||||||
survey.disabled=(O pop-up da pesquisa será desativado nas atualizações seguintes, mas estará disponível no rodapé da página)
|
survey.disabled=(O pop-up da pesquisa será desativado nas atualizações seguintes, mas estará disponível no final da página)
|
||||||
survey.button=Responda a pesquisa
|
survey.button=Responda a pesquisa
|
||||||
survey.dontShowAgain=Não mostre novamente
|
survey.dontShowAgain=Não mostre novamente
|
||||||
|
|
||||||
@@ -1192,11 +1193,11 @@ survey.dontShowAgain=Não mostre novamente
|
|||||||
#error
|
#error
|
||||||
error.sorry=Desculpe pelo problema!
|
error.sorry=Desculpe pelo problema!
|
||||||
error.needHelp=Precisa de ajuda / Encontrou um problema?
|
error.needHelp=Precisa de ajuda / Encontrou um problema?
|
||||||
error.contactTip=Se você ainda estiver com problemas, não hesite em entrar em contato conosco para obter ajuda. Você pode enviar um tíquete em nossa página GitHub ou entrar em contato conosco através do Discord:
|
error.contactTip=Se você ainda estiver com problemas, não hesite em entrar em contato conosco para obter ajuda. Você pode enviar um ticket em nossa página GitHub ou entrar em contato conosco através do Discord:
|
||||||
error.404.head=404 - Página não encontrada | Ops, tropeçamos no código!
|
error.404.head=404 - Página não encontrada | Ops, tropeçamos no código!
|
||||||
error.404.1=Não conseguimos encontrar a página que você está procurando.
|
error.404.1=Não conseguimos encontrar a página que você está procurando.
|
||||||
error.404.2=Algo deu errado
|
error.404.2=Algo deu errado
|
||||||
error.github=Submeter um tíquete no GitHub
|
error.github=Submeter um ticket no GitHub
|
||||||
error.showStack=Mostrar rastreamento de pilha
|
error.showStack=Mostrar rastreamento de pilha
|
||||||
error.copyStack=Copiar rastreamento de pilha
|
error.copyStack=Copiar rastreamento de pilha
|
||||||
error.githubSubmit=GitHub - Submeter um tíquete
|
error.githubSubmit=GitHub - Submeter um tíquete
|
||||||
@@ -1204,21 +1205,21 @@ error.discordSubmit=Discord - Submeter um post de suporte
|
|||||||
|
|
||||||
|
|
||||||
#remove-image
|
#remove-image
|
||||||
removeImage.title=Remover Imagem
|
removeImage.title=Remover imagem
|
||||||
removeImage.header=Remover imagem
|
removeImage.header=Remover imagem
|
||||||
removeImage.removeImage=Remover imagem
|
removeImage.removeImage=Remover imagem
|
||||||
removeImage.submit=Remover imagem
|
removeImage.submit=Remover imagem
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=Dividir PDF por Capítulos
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=Dividir PDF por Capítulos
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=Nível de Marcador
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=Incluir Metadados
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=Permitir Cópias
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=Essa ferramenta divide um arquivo PDF em vários arquivos PDFs baseado na estrutura de cápitulos.
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=Nível de Marcador: Escolha o nível de marcador a ser usado para divisão (0 para o primeiro nível, 1 para o segundo nível, etc).
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=Incluir Metadados: Se marcado, os metadados do PDF original serão incluidos em cada divisão do PDF.
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=Permitir Cópias: Se marcado, habilita vários marcadores na mesma página para criar PDFs separados.
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=Dividir PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Edição Automática
|
autoRedact.title=Edição Automática
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=Utilizatorul este dezactivat, conectarea este în prezent b
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Redactare Automată
|
autoRedact.title=Redactare Automată
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Автоматическое редактирование
|
autoRedact.title=Автоматическое редактирование
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Automatické redigovanie
|
autoRedact.title=Automatické redigovanie
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto Cenzura
|
autoRedact.title=Auto Cenzura
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=Användaren är inaktiverad, inloggning är för närvarand
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Auto-redigera
|
autoRedact.title=Auto-redigera
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=ซ่อนข้อมูลอัตโนมัติ
|
autoRedact.title=ซ่อนข้อมูลอัตโนมัติ
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=Kullanıcı devre dışı bırakıldı, şu anda bu kullan
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Otomatik Karartma
|
autoRedact.title=Otomatik Karartma
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Автоматичне редагування
|
autoRedact.title=Автоматичне редагування
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=User is deactivated, login is currently blocked with this u
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=Tự động biên tập
|
autoRedact.title=Tự động biên tập
|
||||||
|
|||||||
@@ -554,6 +554,7 @@ login.userIsDisabled=用户被禁用,登录已被阻止。请联系管理员
|
|||||||
login.alreadyLoggedIn=You are already logged in to
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=You have too many active sessions
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=自动删除
|
autoRedact.title=自动删除
|
||||||
|
|||||||
@@ -3,8 +3,8 @@
|
|||||||
###########
|
###########
|
||||||
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
# the direction that the language is written (ltr = left to right, rtl = right to left)
|
||||||
language.direction=ltr
|
language.direction=ltr
|
||||||
addPageNumbers.fontSize=字型大小
|
addPageNumbers.fontSize=Font Size
|
||||||
addPageNumbers.fontName=字型名稱
|
addPageNumbers.fontName=Font Name
|
||||||
pdfPrompt=選擇 PDF 檔案
|
pdfPrompt=選擇 PDF 檔案
|
||||||
multiPdfPrompt=選擇多個 PDF 檔案
|
multiPdfPrompt=選擇多個 PDF 檔案
|
||||||
multiPdfDropPrompt=選擇(或拖放)所有需要的 PDF 檔案
|
multiPdfDropPrompt=選擇(或拖放)所有需要的 PDF 檔案
|
||||||
@@ -12,7 +12,7 @@ imgPrompt=選擇圖片
|
|||||||
genericSubmit=送出
|
genericSubmit=送出
|
||||||
processTimeWarning=警告:此過程可能長達一分鐘,具體取決於檔案大小
|
processTimeWarning=警告:此過程可能長達一分鐘,具體取決於檔案大小
|
||||||
pageOrderPrompt=自訂頁面順序(輸入以逗號分隔的頁碼或函式,如 2n+1):
|
pageOrderPrompt=自訂頁面順序(輸入以逗號分隔的頁碼或函式,如 2n+1):
|
||||||
pageSelectionPrompt=自訂頁面選擇(輸入以逗號分隔的頁碼 1、5、6 或 2n+1 等函式的清單):
|
pageSelectionPrompt=自訂頁面選擇(輸入以逗號分隔的頁碼 1、5、6 或 2n+1 等函數的清單):
|
||||||
goToPage=前往
|
goToPage=前往
|
||||||
true=是
|
true=是
|
||||||
false=否
|
false=否
|
||||||
@@ -21,7 +21,7 @@ save=儲存
|
|||||||
saveToBrowser=儲存到瀏覽器
|
saveToBrowser=儲存到瀏覽器
|
||||||
close=關閉
|
close=關閉
|
||||||
filesSelected=已選擇的檔案
|
filesSelected=已選擇的檔案
|
||||||
noFavourites=還沒有功能被收藏
|
noFavourites=未新增收藏
|
||||||
downloadComplete=下載完成
|
downloadComplete=下載完成
|
||||||
bored=等待時覺得無聊?
|
bored=等待時覺得無聊?
|
||||||
alphabet=字母表
|
alphabet=字母表
|
||||||
@@ -51,62 +51,62 @@ poweredBy=Powered by
|
|||||||
yes=是
|
yes=是
|
||||||
no=否
|
no=否
|
||||||
changedCredsMessage=憑證已變更!
|
changedCredsMessage=憑證已變更!
|
||||||
notAuthenticatedMessage=使用者未通過驗證。
|
notAuthenticatedMessage=使用者未認證。
|
||||||
userNotFoundMessage=找不到使用者。
|
userNotFoundMessage=找不到使用者。
|
||||||
incorrectPasswordMessage=目前密碼不正確。
|
incorrectPasswordMessage=目前密碼不正確。
|
||||||
usernameExistsMessage=新使用者名稱已存在。
|
usernameExistsMessage=新使用者名稱已存在。
|
||||||
invalidUsernameMessage=無效的使用者名稱。使用者名稱只能包含字母、數字和以下特殊字元 @._+- 或必須是有效的電子郵件地址。
|
invalidUsernameMessage=使用者名稱無效,使用者名稱只能包含字母、數字和以下特殊字元 @._+- 或必須是有效的電子郵件地址。
|
||||||
invalidPasswordMessage=密碼不能為空,且開頭和結尾不能有空格。
|
invalidPasswordMessage=The password must not be empty and must not have spaces at the beginning or end.
|
||||||
confirmPasswordErrorMessage=新密碼與確認新密碼必須相符。
|
confirmPasswordErrorMessage=輸入的密碼必需和確認密碼相同。
|
||||||
deleteCurrentUserMessage=無法刪除目前登入的使用者。
|
deleteCurrentUserMessage=無法刪除目前登錄的使用者。
|
||||||
deleteUsernameExistsMessage=使用者名稱不存在,無法刪除。
|
deleteUsernameExistsMessage=使用者名不存在,無法刪除。
|
||||||
downgradeCurrentUserMessage=無法降級目前使用者的角色
|
downgradeCurrentUserMessage=無法降級目前使用者的角色
|
||||||
disabledCurrentUserMessage=無法停用目前使用者
|
disabledCurrentUserMessage=The current user cannot be disabled
|
||||||
downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,將不會顯示目前使用者。
|
downgradeCurrentUserLongMessage=無法降級目前使用者的角色。因此,不會顯示目前的使用者。
|
||||||
userAlreadyExistsOAuthMessage=使用者已經以 OAuth2 使用者身份存在。
|
userAlreadyExistsOAuthMessage=該使用者已於 OAuth2 註冊。
|
||||||
userAlreadyExistsWebMessage=使用者已經以網頁使用者身份存在。
|
userAlreadyExistsWebMessage=該使用者已於網頁註冊。
|
||||||
error=錯誤
|
error=錯誤
|
||||||
oops=哎呀!
|
oops=哎呀!
|
||||||
help=說明
|
help=幫助
|
||||||
goHomepage=前往首頁
|
goHomepage=前往首頁
|
||||||
joinDiscord=加入我們的 Discord 伺服器
|
joinDiscord=加入我們的 Discord 伺服器
|
||||||
seeDockerHub=造訪 Docker Hub 儲存庫
|
seeDockerHub=查看 Docker Hub
|
||||||
visitGithub=造訪 GitHub 專案
|
visitGithub=訪問 GitHub 存儲庫
|
||||||
donate=捐款
|
donate=捐贈
|
||||||
color=顏色
|
color=顏色
|
||||||
sponsor=贊助
|
sponsor=贊助
|
||||||
info=資訊
|
info=Info
|
||||||
pro=專業版
|
pro=Pro
|
||||||
page=頁面
|
page=Page
|
||||||
pages=頁面
|
pages=Pages
|
||||||
|
|
||||||
legal.privacy=隱私權政策
|
legal.privacy=Privacy Policy
|
||||||
legal.terms=使用條款
|
legal.terms=Terms and Conditions
|
||||||
legal.accessibility=無障礙性聲明
|
legal.accessibility=Accessibility
|
||||||
legal.cookie=Cookie 政策
|
legal.cookie=Cookie Policy
|
||||||
legal.impressum=版本說明
|
legal.impressum=Impressum
|
||||||
|
|
||||||
###############
|
###############
|
||||||
# Pipeline #
|
# Pipeline #
|
||||||
###############
|
###############
|
||||||
pipeline.header=管道功能選單(測試版)
|
pipeline.header=管道選單(測試版)
|
||||||
pipeline.uploadButton=上傳自訂設定
|
pipeline.uploadButton=上傳自定義
|
||||||
pipeline.configureButton=設定
|
pipeline.configureButton=配置
|
||||||
pipeline.defaultOption=自訂
|
pipeline.defaultOption=自訂
|
||||||
pipeline.submitButton=送出
|
pipeline.submitButton=送出
|
||||||
pipeline.help=管道功能說明
|
pipeline.help=管道説明
|
||||||
pipeline.scanHelp=資料夾掃描說明
|
pipeline.scanHelp=資料夾掃描説明
|
||||||
pipeline.deletePrompt=您確定要刪除此管道嗎?
|
pipeline.deletePrompt=確定刪除該管道?
|
||||||
|
|
||||||
######################
|
######################
|
||||||
# Pipeline Options #
|
# Pipeline Options #
|
||||||
######################
|
######################
|
||||||
pipelineOptions.header=管道設定
|
pipelineOptions.header=管道配置
|
||||||
pipelineOptions.pipelineNameLabel=管道名稱
|
pipelineOptions.pipelineNameLabel=管道名稱
|
||||||
pipelineOptions.saveSettings=儲存操作設定
|
pipelineOptions.saveSettings=保存操作設置
|
||||||
pipelineOptions.pipelineNamePrompt=請在此輸入管道名稱
|
pipelineOptions.pipelineNamePrompt=在此處輸入管道名稱
|
||||||
pipelineOptions.selectOperation=選擇操作
|
pipelineOptions.selectOperation=選擇操作
|
||||||
pipelineOptions.addOperationButton=新增操作
|
pipelineOptions.addOperationButton=添加操作
|
||||||
pipelineOptions.pipelineHeader=管道:
|
pipelineOptions.pipelineHeader=管道:
|
||||||
pipelineOptions.saveButton=下載
|
pipelineOptions.saveButton=下載
|
||||||
pipelineOptions.validateButton=驗證
|
pipelineOptions.validateButton=驗證
|
||||||
@@ -114,21 +114,21 @@ pipelineOptions.validateButton=驗證
|
|||||||
########################
|
########################
|
||||||
# ENTERPRISE EDITION #
|
# ENTERPRISE EDITION #
|
||||||
########################
|
########################
|
||||||
enterpriseEdition.button=升級至專業版
|
enterpriseEdition.button=Upgrade to Pro
|
||||||
enterpriseEdition.warning=此功能僅提供給專業版使用者使用。
|
enterpriseEdition.warning=This feature is only available to Pro users.
|
||||||
enterpriseEdition.yamlAdvert=Stirling PDF 專業版支援 YAML 設定檔和其他單一登入 (SSO) 功能。
|
enterpriseEdition.yamlAdvert=Stirling PDF Pro supports YAML configuration files and other SSO features.
|
||||||
enterpriseEdition.ssoAdvert=需要更多使用者管理功能嗎?請參考 Stirling PDF 專業版
|
enterpriseEdition.ssoAdvert=Looking for more user management features? Check out Stirling PDF Pro
|
||||||
|
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Analytics #
|
# Analytics #
|
||||||
#################
|
#################
|
||||||
analytics.title=您想協助改善 Stirling PDF 嗎?
|
analytics.title=Do you want make Stirling PDF better?
|
||||||
analytics.paragraph1=Stirling PDF 有選擇性的分析功能,可幫助我們改進產品。我們不會追蹤任何個人資訊或檔案內容。
|
analytics.paragraph1=Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents.
|
||||||
analytics.paragraph2=請考慮啟用分析功能,以協助 Stirling-PDF 成長並讓我們更了解使用者需求。
|
analytics.paragraph2=Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.
|
||||||
analytics.enable=啟用分析功能
|
analytics.enable=Enable analytics
|
||||||
analytics.disable=停用分析功能
|
analytics.disable=Disable analytics
|
||||||
analytics.settings=您可以在 config/settings.yml 檔案中變更分析功能的設定
|
analytics.settings=You can change the settings for analytics in the config/settings.yml file
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# NAVBAR #
|
# NAVBAR #
|
||||||
@@ -138,36 +138,36 @@ navbar.darkmode=深色模式
|
|||||||
navbar.language=語言
|
navbar.language=語言
|
||||||
navbar.settings=設定
|
navbar.settings=設定
|
||||||
navbar.allTools=工具
|
navbar.allTools=工具
|
||||||
navbar.multiTool=多功能工具
|
navbar.multiTool=萬用工具
|
||||||
navbar.sections.organize=整理
|
navbar.sections.organize=組織
|
||||||
navbar.sections.convertTo=轉換為 PDF
|
navbar.sections.convertTo=轉換為 PDF
|
||||||
navbar.sections.convertFrom=從 PDF 轉換
|
navbar.sections.convertFrom=從 PDF 轉換
|
||||||
navbar.sections.security=簽章與安全性
|
navbar.sections.security=簽章與安全性
|
||||||
navbar.sections.advance=進階
|
navbar.sections.advance=進階
|
||||||
navbar.sections.edit=檢視與編輯
|
navbar.sections.edit=檢視與編輯
|
||||||
navbar.sections.popular=熱門功能
|
navbar.sections.popular=Popular
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# SETTINGS #
|
# SETTINGS #
|
||||||
#############
|
#############
|
||||||
settings.title=設定
|
settings.title=設定
|
||||||
settings.update=有更新可用
|
settings.update=有更新可用
|
||||||
settings.updateAvailable=目前安裝的版本是 {0}。有新版本({1})可供使用。
|
settings.updateAvailable=當前版本為 {0}。歡迎你更新至最新版 ({1})。。
|
||||||
settings.appVersion=應用程式版本:
|
settings.appVersion=應用版本:
|
||||||
settings.downloadOption.title=選擇下載選項(適用於單一檔案非壓縮下載):
|
settings.downloadOption.title=選擇下載選項(對於單一檔案非壓縮下載):
|
||||||
settings.downloadOption.1=在同一視窗中開啟
|
settings.downloadOption.1=在同一視窗中開啟
|
||||||
settings.downloadOption.2=在新視窗中開啟
|
settings.downloadOption.2=在新視窗中開啟
|
||||||
settings.downloadOption.3=下載檔案
|
settings.downloadOption.3=下載檔案
|
||||||
settings.zipThreshold=當下載檔案數量超過此數值時,將檔案壓縮
|
settings.zipThreshold=當下載的檔案數量超過時,壓縮檔案
|
||||||
settings.signOut=登出
|
settings.signOut=登出
|
||||||
settings.accountSettings=帳號設定
|
settings.accountSettings=帳戶設定
|
||||||
settings.bored.help=啟用彩蛋遊戲
|
settings.bored.help=啟用彩蛋
|
||||||
settings.cacheInputs.name=儲存表單輸入
|
settings.cacheInputs.name=輸入檔案下載
|
||||||
settings.cacheInputs.help=啟用此功能以儲存先前使用的輸入,以便日後使用
|
settings.cacheInputs.help=開啟記住先前的輸入,做為日後使用
|
||||||
|
|
||||||
changeCreds.title=變更憑證
|
changeCreds.title=變更憑證
|
||||||
changeCreds.header=更新您的帳號詳細資訊
|
changeCreds.header=更新你的帳戶詳細資訊
|
||||||
changeCreds.changePassword=您正在使用預設登入憑證。請輸入新密碼
|
changeCreds.changePassword=你使用的是預設登錄認證。請輸入新密碼
|
||||||
changeCreds.newUsername=新使用者名稱
|
changeCreds.newUsername=新使用者名稱
|
||||||
changeCreds.oldPassword=目前密碼
|
changeCreds.oldPassword=目前密碼
|
||||||
changeCreds.newPassword=新密碼
|
changeCreds.newPassword=新密碼
|
||||||
@@ -176,9 +176,9 @@ changeCreds.submit=送出變更
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
account.title=帳號設定
|
account.title=帳戶設定
|
||||||
account.accountSettings=帳號設定
|
account.accountSettings=帳戶設定
|
||||||
account.adminSettings=管理員設定 - 檢視和新增使用者
|
account.adminSettings=管理設定 - 檢視和新增使用者
|
||||||
account.userControlSettings=使用者控制設定
|
account.userControlSettings=使用者控制設定
|
||||||
account.changeUsername=修改使用者名稱
|
account.changeUsername=修改使用者名稱
|
||||||
account.newUsername=新使用者名稱
|
account.newUsername=新使用者名稱
|
||||||
@@ -188,43 +188,43 @@ account.newPassword=新密碼
|
|||||||
account.changePassword=修改密碼
|
account.changePassword=修改密碼
|
||||||
account.confirmNewPassword=確認新密碼
|
account.confirmNewPassword=確認新密碼
|
||||||
account.signOut=登出
|
account.signOut=登出
|
||||||
account.yourApiKey=您的 API 金鑰
|
account.yourApiKey=你的 API 金鑰
|
||||||
account.syncTitle=將瀏覽器設定與帳號同步
|
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=使用者控制設定
|
||||||
adminUserSettings.header=管理員使用者控制設定
|
adminUserSettings.header=管理使用者控制設定
|
||||||
adminUserSettings.admin=管理員
|
adminUserSettings.admin=管理員
|
||||||
adminUserSettings.user=使用者
|
adminUserSettings.user=使用者
|
||||||
adminUserSettings.addUser=新增使用者
|
adminUserSettings.addUser=新增使用者
|
||||||
adminUserSettings.deleteUser=刪除使用者
|
adminUserSettings.deleteUser=刪除使用者
|
||||||
adminUserSettings.confirmDeleteUser=確定要刪除此使用者?
|
adminUserSettings.confirmDeleteUser=確認刪除該使用者?
|
||||||
adminUserSettings.confirmChangeUserStatus=是否要停用/啟用此使用者?
|
adminUserSettings.confirmChangeUserStatus=Should the user be disabled/enabled?
|
||||||
adminUserSettings.usernameInfo=使用者名稱只能包含字母、數字和以下特殊字元 @._+- 或必須是有效的電子郵件地址。
|
adminUserSettings.usernameInfo=使用者名稱只能包含字母、數字和以下特殊字元 @._+-,或必須是有效的電子郵件地址。
|
||||||
adminUserSettings.roles=角色
|
adminUserSettings.roles=角色
|
||||||
adminUserSettings.role=角色
|
adminUserSettings.role=角色
|
||||||
adminUserSettings.actions=操作
|
adminUserSettings.actions=操作
|
||||||
adminUserSettings.apiUser=受限制的 API 使用者
|
adminUserSettings.apiUser=受限制的 API 使用者
|
||||||
adminUserSettings.extraApiUser=額外受限制的 API 使用者
|
adminUserSettings.extraApiUser=其他受限 API 使用者
|
||||||
adminUserSettings.webOnlyUser=僅網頁版使用者
|
adminUserSettings.webOnlyUser=僅使用網頁的使用者
|
||||||
adminUserSettings.demoUser=示範使用者(無自訂設定)
|
adminUserSettings.demoUser=示範用途的使用者(無自訂設定)
|
||||||
adminUserSettings.internalApiUser=內部 API 使用者
|
adminUserSettings.internalApiUser=內部 API 使用者
|
||||||
adminUserSettings.forceChange=強制使用者在登入時變更密碼
|
adminUserSettings.forceChange=強制使用者在登入時修改使用者名稱/密碼
|
||||||
adminUserSettings.submit=儲存使用者
|
adminUserSettings.submit=儲存
|
||||||
adminUserSettings.changeUserRole=變更使用者角色
|
adminUserSettings.changeUserRole=更改使用者身份
|
||||||
adminUserSettings.authenticated=已驗證
|
adminUserSettings.authenticated=已驗證
|
||||||
adminUserSettings.editOwnProfil=編輯自己的個人資料
|
adminUserSettings.editOwnProfil=Edit own profile
|
||||||
adminUserSettings.enabledUser=已啟用使用者
|
adminUserSettings.enabledUser=enabled user
|
||||||
adminUserSettings.disabledUser=已停用使用者
|
adminUserSettings.disabledUser=disabled user
|
||||||
adminUserSettings.activeUsers=使用中的使用者:
|
adminUserSettings.activeUsers=Active Users:
|
||||||
adminUserSettings.disabledUsers=已停用的使用者:
|
adminUserSettings.disabledUsers=Disabled Users:
|
||||||
adminUserSettings.totalUsers=使用者總數:
|
adminUserSettings.totalUsers=Total Users:
|
||||||
adminUserSettings.lastRequest=最後請求時間
|
adminUserSettings.lastRequest=Last Request
|
||||||
|
|
||||||
|
|
||||||
database.title=資料庫匯入/匯出
|
database.title=資料庫匯入/匯出
|
||||||
@@ -235,20 +235,20 @@ database.fileSize=檔案大小
|
|||||||
database.deleteBackupFile=刪除備份檔案
|
database.deleteBackupFile=刪除備份檔案
|
||||||
database.importBackupFile=匯入備份檔案
|
database.importBackupFile=匯入備份檔案
|
||||||
database.downloadBackupFile=下載備份檔案
|
database.downloadBackupFile=下載備份檔案
|
||||||
database.info_1=在匯入資料時,確保正確的結構至關重要。如果您不確定自己在做什麼,請尋求專業人士的建議和支援。結構錯誤可能會導致應用程式故障,甚至完全無法執行應用程式。
|
database.info_1=在匯入資料時,確保正確的結構是至關重要的。如果你不確定自己在做什麼,請尋求專業人士的建議和支持。結構錯誤可能會導致應用程式故障,甚至完全無法運行。
|
||||||
database.info_2=上傳時檔案名稱並不重要。上傳後將重新命名為 backup_user_yyyyMMddHHmm.sql 格式,以確保命名規範一致。
|
database.info_2=上傳時檔案名稱無關緊要。上傳後將重新命名為 backup_user_yyyyMMddHHmm.sql 格式,以確保命名規範一致。
|
||||||
database.submit=匯入備份
|
database.submit=匯入備份
|
||||||
database.importIntoDatabaseSuccessed=成功匯入資料庫
|
database.importIntoDatabaseSuccessed=成功匯入資料庫
|
||||||
database.fileNotFound=找不到檔案
|
database.fileNotFound=檔案未找到
|
||||||
database.fileNullOrEmpty=檔案不得為空或空白
|
database.fileNullOrEmpty=檔案不能為空或空白
|
||||||
database.failedImportFile=匯入檔案失敗
|
database.failedImportFile=匯入檔案失敗
|
||||||
|
|
||||||
session.expired=您的工作階段已過期。請重新整理頁面並再試一次。
|
session.expired=Your session has expired. Please refresh the page and try again.
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# HOME-PAGE #
|
# HOME-PAGE #
|
||||||
#############
|
#############
|
||||||
home.desc=你的本機主機一站式 PDF 需求解決方案。
|
home.desc=你的本地主機一站式 PDF 需求解決方案。
|
||||||
home.searchBar=搜尋功能...
|
home.searchBar=搜尋功能...
|
||||||
|
|
||||||
|
|
||||||
@@ -256,9 +256,9 @@ home.viewPdf.title=檢視 PDF
|
|||||||
home.viewPdf.desc=檢視、註釋、新增文字或圖片
|
home.viewPdf.desc=檢視、註釋、新增文字或圖片
|
||||||
viewPdf.tags=檢視,閱讀,註釋,文字,圖片
|
viewPdf.tags=檢視,閱讀,註釋,文字,圖片
|
||||||
|
|
||||||
home.multiTool.title=PDF 複合工具
|
home.multiTool.title=PDF 多工具
|
||||||
home.multiTool.desc=合併、旋轉、重新排列和移除頁面
|
home.multiTool.desc=合併、旋轉、重新排列和移除頁面
|
||||||
multiTool.tags=複合工具,多功能,UI,點選拖曳,前端,客戶端,互動,互動式,移動
|
multiTool.tags=多工具,多操作,UI,點選拖動,前端,客戶端,互動,可互動,移動
|
||||||
|
|
||||||
home.merge.title=合併
|
home.merge.title=合併
|
||||||
home.merge.desc=輕鬆將多個 PDF 合併為一個。
|
home.merge.desc=輕鬆將多個 PDF 合併為一個。
|
||||||
@@ -392,7 +392,7 @@ certSign.tags=驗證,PEM,P12,官方,加密
|
|||||||
|
|
||||||
home.removeCertSign.title=移除簽章
|
home.removeCertSign.title=移除簽章
|
||||||
home.removeCertSign.desc=從 PDF 移除簽章
|
home.removeCertSign.desc=從 PDF 移除簽章
|
||||||
removeCertSign.tags=驗證,PEM,P12,官方,解密
|
removeCertSign.tags=authenticate,PEM,P12,official,decrypt
|
||||||
|
|
||||||
home.pageLayout.title=多頁面版面配置
|
home.pageLayout.title=多頁面版面配置
|
||||||
home.pageLayout.desc=將 PDF 檔案的多個頁面合併到單一頁面
|
home.pageLayout.desc=將 PDF 檔案的多個頁面合併到單一頁面
|
||||||
@@ -485,9 +485,9 @@ home.split-by-sections.title=依區段分割 PDF
|
|||||||
home.split-by-sections.desc=將 PDF 的每一頁分割為較小的水平和垂直區段
|
home.split-by-sections.desc=將 PDF 的每一頁分割為較小的水平和垂直區段
|
||||||
split-by-sections.tags=區段分割, 劃分, 自訂
|
split-by-sections.tags=區段分割, 劃分, 自訂
|
||||||
|
|
||||||
home.AddStampRequest.title=將圖章新增到 PDF
|
home.AddStampRequest.title=將圖章添加到 PDF
|
||||||
home.AddStampRequest.desc=在設定位置新增文字或新增影像圖章
|
home.AddStampRequest.desc=在設置位置添加文字或添加圖像圖章
|
||||||
AddStampRequest.tags=圖章,新增圖片,中心影像,浮水印,PDF,嵌入,自訂
|
AddStampRequest.tags=圖章,添加圖片,中心圖像,浮水印,PDF,嵌入,自訂
|
||||||
|
|
||||||
|
|
||||||
home.PDFToBook.title=PDF 轉電子書
|
home.PDFToBook.title=PDF 轉電子書
|
||||||
@@ -498,33 +498,33 @@ home.BookToPDF.title=電子書轉 PDF
|
|||||||
home.BookToPDF.desc=使用 calibre 將書籍/漫畫格式轉換為 PDF
|
home.BookToPDF.desc=使用 calibre 將書籍/漫畫格式轉換為 PDF
|
||||||
BookToPDF.tags=電子書,漫畫,Calibre,轉換,日本漫畫,亞馬遜,kindle
|
BookToPDF.tags=電子書,漫畫,Calibre,轉換,日本漫畫,亞馬遜,kindle
|
||||||
|
|
||||||
home.removeImagePdf.title=移除圖片
|
home.removeImagePdf.title=Remove image
|
||||||
home.removeImagePdf.desc=從 PDF 中移除圖片以減少檔案大小
|
home.removeImagePdf.desc=Remove image from PDF to reduce file size
|
||||||
removeImagePdf.tags=移除圖片,頁面操作,後端,伺服器端
|
removeImagePdf.tags=Remove Image,Page operations,Back end,server side
|
||||||
|
|
||||||
|
|
||||||
home.splitPdfByChapters.title=依章節分割 PDF
|
home.splitPdfByChapters.title=Split PDF by Chapters
|
||||||
home.splitPdfByChapters.desc=根據 PDF 的章節結構將其分割成多個檔案。
|
home.splitPdfByChapters.desc=Split a PDF into multiple files based on its chapter structure.
|
||||||
splitPdfByChapters.tags=分割,章節,書籤,整理
|
splitPdfByChapters.tags=split,chapters,bookmarks,organize
|
||||||
|
|
||||||
#replace-invert-color
|
#replace-invert-color
|
||||||
replace-color.title=取代-反轉顏色
|
replace-color.title=Replace-Invert-Color
|
||||||
replace-color.header=取代-反轉 PDF 顏色
|
replace-color.header=Replace-Invert Color PDF
|
||||||
home.replaceColorPdf.title=取代與反轉顏色
|
home.replaceColorPdf.title=Replace and Invert Color
|
||||||
home.replaceColorPdf.desc=取代 PDF 中文字和背景的顏色,並反轉整個 PDF 的顏色以減少檔案大小
|
home.replaceColorPdf.desc=Replace color for text and background in PDF and invert full color of pdf to reduce file size
|
||||||
replaceColorPdf.tags=取代顏色,頁面操作,後端,伺服器端
|
replaceColorPdf.tags=Replace Color,Page operations,Back end,server side
|
||||||
replace-color.selectText.1=取代或反轉顏色選項
|
replace-color.selectText.1=Replace or Invert color Options
|
||||||
replace-color.selectText.2=預設(預設高對比度顏色)
|
replace-color.selectText.2=Default(Default high contrast colors)
|
||||||
replace-color.selectText.3=自訂(自訂顏色)
|
replace-color.selectText.3=Custom(Customized colors)
|
||||||
replace-color.selectText.4=全部反轉(反轉所有顏色)
|
replace-color.selectText.4=Full-Invert(Invert all colors)
|
||||||
replace-color.selectText.5=高對比度顏色選項
|
replace-color.selectText.5=High contrast color options
|
||||||
replace-color.selectText.6=黑底白字
|
replace-color.selectText.6=white text on black background
|
||||||
replace-color.selectText.7=白底黑字
|
replace-color.selectText.7=Black text on white background
|
||||||
replace-color.selectText.8=黑底黃字
|
replace-color.selectText.8=Yellow text on black background
|
||||||
replace-color.selectText.9=黑底綠字
|
replace-color.selectText.9=Green text on black background
|
||||||
replace-color.selectText.10=選擇文字顏色
|
replace-color.selectText.10=Choose text Color
|
||||||
replace-color.selectText.11=選擇背景顏色
|
replace-color.selectText.11=Choose background Color
|
||||||
replace-color.submit=取代
|
replace-color.submit=Replace
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -542,18 +542,19 @@ login.invalid=使用者名稱或密碼無效。
|
|||||||
login.locked=你的帳戶已被鎖定。
|
login.locked=你的帳戶已被鎖定。
|
||||||
login.signinTitle=請登入
|
login.signinTitle=請登入
|
||||||
login.ssoSignIn=透過織網單一簽入
|
login.ssoSignIn=透過織網單一簽入
|
||||||
login.oauth2AutoCreateDisabled=OAuth 2.0 自動建立使用者功能已停用
|
login.oauth2AutoCreateDisabled=OAuth 2.0 自動建立使用者已停用
|
||||||
login.oauth2AdminBlockedUser=目前已封鎖非註冊使用者的註冊或登入。請聯絡系統管理員。
|
login.oauth2AdminBlockedUser=Registration or logging in of non-registered users is currently blocked. Please contact the administrator.
|
||||||
login.oauth2RequestNotFound=找不到驗證請求
|
login.oauth2RequestNotFound=找不到驗證請求
|
||||||
login.oauth2InvalidUserInfoResponse=無效的使用者資訊回應
|
login.oauth2InvalidUserInfoResponse=無效的使用者資訊回應
|
||||||
login.oauth2invalidRequest=無效的回應
|
login.oauth2invalidRequest=無效的回應
|
||||||
login.oauth2AccessDenied=存取被拒
|
login.oauth2AccessDenied=存取被拒
|
||||||
login.oauth2InvalidTokenResponse=無效的權杖回應
|
login.oauth2InvalidTokenResponse=無效的 Token 回應
|
||||||
login.oauth2InvalidIdToken=無效的識別權杖
|
login.oauth2InvalidIdToken=無效的 Tokne
|
||||||
login.userIsDisabled=使用者已停用,目前此使用者無法登入。請聯絡系統管理員。
|
login.userIsDisabled=User is deactivated, login is currently blocked with this username. Please contact the administrator.
|
||||||
login.alreadyLoggedIn=您已經登入了
|
login.alreadyLoggedIn=You are already logged in to
|
||||||
login.alreadyLoggedIn2=個裝置。請登出其他裝置後再試一次。
|
login.alreadyLoggedIn2=devices. Please log out of the devices and try again.
|
||||||
login.toManySessions=您有太多使用中的工作階段
|
login.toManySessions=You have too many active sessions
|
||||||
|
login.toManySessions2=Please log out of the devices and try again. Alternatively, you can upgrade to Stirling PDF Pro.
|
||||||
|
|
||||||
#auto-redact
|
#auto-redact
|
||||||
autoRedact.title=自動塗黑
|
autoRedact.title=自動塗黑
|
||||||
@@ -639,7 +640,7 @@ AddStampRequest.stampType=圖章類型
|
|||||||
AddStampRequest.stampText=圖章文字
|
AddStampRequest.stampText=圖章文字
|
||||||
AddStampRequest.stampImage=圖章圖片
|
AddStampRequest.stampImage=圖章圖片
|
||||||
AddStampRequest.alphabet=字母表
|
AddStampRequest.alphabet=字母表
|
||||||
AddStampRequest.fontSize=字型/影像大小
|
AddStampRequest.fontSize=字體/圖像大小
|
||||||
AddStampRequest.rotation=旋轉
|
AddStampRequest.rotation=旋轉
|
||||||
AddStampRequest.opacity=透明度
|
AddStampRequest.opacity=透明度
|
||||||
AddStampRequest.position=位置
|
AddStampRequest.position=位置
|
||||||
@@ -728,7 +729,7 @@ pageLayout.submit=送出
|
|||||||
scalePages.title=調整頁面大小/比例
|
scalePages.title=調整頁面大小/比例
|
||||||
scalePages.header=調整頁面大小/比例
|
scalePages.header=調整頁面大小/比例
|
||||||
scalePages.pageSize=文件的頁面大小。
|
scalePages.pageSize=文件的頁面大小。
|
||||||
scalePages.keepPageSize=原始大小
|
scalePages.keepPageSize=Original Size
|
||||||
scalePages.scaleFactor=頁面的縮放級別(裁剪)。
|
scalePages.scaleFactor=頁面的縮放級別(裁剪)。
|
||||||
scalePages.submit=送出
|
scalePages.submit=送出
|
||||||
|
|
||||||
@@ -737,7 +738,7 @@ scalePages.submit=送出
|
|||||||
certSign.title=憑證簽章
|
certSign.title=憑證簽章
|
||||||
certSign.header=使用你的憑證簽章(進行中)
|
certSign.header=使用你的憑證簽章(進行中)
|
||||||
certSign.selectPDF=選擇要簽章的 PDF 檔案:
|
certSign.selectPDF=選擇要簽章的 PDF 檔案:
|
||||||
certSign.jksNote=注意:如果你的證書類型未在下面列出,請使用 keytool 命令列工具將其轉換為 Java Keystore (.jks) 檔。 然後,選擇下面的 .jks 文件選項。
|
certSign.jksNote=注意:如果你的證書類型未在下面列出,請使用 keytool 命令行工具將其轉換為 Java Keystore (.jks) 檔。 然後,選擇下面的 .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 金鑰庫文件(.p12 或 .pfx)(可選,如果提供,它應包含你的私鑰和憑證):
|
certSign.selectP12=選擇你的 PKCS#12 金鑰庫文件(.p12 或 .pfx)(可選,如果提供,它應包含你的私鑰和憑證):
|
||||||
@@ -777,8 +778,8 @@ removeAnnotations.submit=移除
|
|||||||
#compare
|
#compare
|
||||||
compare.title=比較
|
compare.title=比較
|
||||||
compare.header=比較 PDF
|
compare.header=比較 PDF
|
||||||
compare.highlightColor.1=標示顏色 1:
|
compare.highlightColor.1=Highlight Color 1:
|
||||||
compare.highlightColor.2=標示顏色 2:
|
compare.highlightColor.2=Highlight Color 2:
|
||||||
compare.document.1=文件 1
|
compare.document.1=文件 1
|
||||||
compare.document.2=文件 2
|
compare.document.2=文件 2
|
||||||
compare.submit=比較
|
compare.submit=比較
|
||||||
@@ -815,7 +816,7 @@ repair.submit=修復
|
|||||||
#flatten
|
#flatten
|
||||||
flatten.title=平坦化
|
flatten.title=平坦化
|
||||||
flatten.header=PDF 平坦化
|
flatten.header=PDF 平坦化
|
||||||
flatten.flattenOnlyForms=僅將表單平坦化
|
flatten.flattenOnlyForms=Flatten only forms
|
||||||
flatten.submit=平坦化
|
flatten.submit=平坦化
|
||||||
|
|
||||||
|
|
||||||
@@ -830,7 +831,7 @@ ScannerImageSplit.selectText.7=最小輪廓區域:
|
|||||||
ScannerImageSplit.selectText.8=設定照片的最小輪廓區域閾值
|
ScannerImageSplit.selectText.8=設定照片的最小輪廓區域閾值
|
||||||
ScannerImageSplit.selectText.9=邊框大小:
|
ScannerImageSplit.selectText.9=邊框大小:
|
||||||
ScannerImageSplit.selectText.10=設定新增和移除的邊框大小,以防止輸出中的白色邊框(預設:1)。
|
ScannerImageSplit.selectText.10=設定新增和移除的邊框大小,以防止輸出中的白色邊框(預設:1)。
|
||||||
ScannerImageSplit.info=尚未安裝 Python。需要安裝 Python 才能執行。
|
ScannerImageSplit.info=Python is not installed. It is required to run.
|
||||||
|
|
||||||
|
|
||||||
#OCR
|
#OCR
|
||||||
@@ -839,8 +840,8 @@ ocr.header=清理掃描 / OCR(光學字元識別)
|
|||||||
ocr.selectText.1=選擇要在 PDF 中偵測的語言(列出的是目前可以偵測的語言):
|
ocr.selectText.1=選擇要在 PDF 中偵測的語言(列出的是目前可以偵測的語言):
|
||||||
ocr.selectText.2=產生包含 OCR 文字的文字文件,並與 OCR 的 PDF 一起
|
ocr.selectText.2=產生包含 OCR 文字的文字文件,並與 OCR 的 PDF 一起
|
||||||
ocr.selectText.3=修正掃描的頁面傾斜角度,將它們旋轉回原位
|
ocr.selectText.3=修正掃描的頁面傾斜角度,將它們旋轉回原位
|
||||||
ocr.selectText.4=清理頁面以降低 OCR 在背景雜訊中識別文字的機率。(無輸出變化)
|
ocr.selectText.4=清理頁面,使 OCR 不太可能在背景噪音中找到文字。(無輸出變化)
|
||||||
ocr.selectText.5=清理頁面以降低 OCR 在背景雜訊中識別文字的機率,保持乾淨的輸出。
|
ocr.selectText.5=清理頁面,使 OCR 不太可能在背景噪音中找到文字,保持清理的輸出。
|
||||||
ocr.selectText.6=忽略具有互動文字的頁面,只對影像頁面進行 OCR
|
ocr.selectText.6=忽略具有互動文字的頁面,只對影像頁面進行 OCR
|
||||||
ocr.selectText.7=強制 OCR,將對每一頁進行 OCR,移除所有原始文字元素
|
ocr.selectText.7=強制 OCR,將對每一頁進行 OCR,移除所有原始文字元素
|
||||||
ocr.selectText.8=正常(如果 PDF 包含文字將出錯)
|
ocr.selectText.8=正常(如果 PDF 包含文字將出錯)
|
||||||
@@ -857,7 +858,7 @@ ocr.submit=使用 OCR 處理 PDF
|
|||||||
extractImages.title=提取圖片
|
extractImages.title=提取圖片
|
||||||
extractImages.header=提取圖片
|
extractImages.header=提取圖片
|
||||||
extractImages.selectText=選擇要轉換提取影像的影像格式
|
extractImages.selectText=選擇要轉換提取影像的影像格式
|
||||||
extractImages.allowDuplicates=儲存重複的圖片
|
extractImages.allowDuplicates=Save duplicate images
|
||||||
extractImages.submit=提取
|
extractImages.submit=提取
|
||||||
|
|
||||||
|
|
||||||
@@ -875,10 +876,10 @@ compress.title=壓縮
|
|||||||
compress.header=壓縮 PDF
|
compress.header=壓縮 PDF
|
||||||
compress.credit=此服務使用 Ghostscript 進行 PDF 壓縮/最佳化。
|
compress.credit=此服務使用 Ghostscript 進行 PDF 壓縮/最佳化。
|
||||||
compress.selectText.1=手動模式 - 從 1 到 4
|
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 大小(例如 25MB, 10.8MB, 25KB)
|
||||||
compress.submit=壓縮
|
compress.submit=壓縮
|
||||||
|
|
||||||
|
|
||||||
@@ -913,14 +914,14 @@ pdfOrganiser.mode.6=奇偶拆分
|
|||||||
pdfOrganiser.mode.7=刪除第一頁
|
pdfOrganiser.mode.7=刪除第一頁
|
||||||
pdfOrganiser.mode.8=刪除最後一頁
|
pdfOrganiser.mode.8=刪除最後一頁
|
||||||
pdfOrganiser.mode.9=刪除第一頁和最後一頁
|
pdfOrganiser.mode.9=刪除第一頁和最後一頁
|
||||||
pdfOrganiser.mode.10=奇偶合併
|
pdfOrganiser.mode.10=Odd-Even Merge
|
||||||
pdfOrganiser.placeholder=(例如 1,3,2 或 4-8,2,10-12 或 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=檔名
|
multiTool.uploadPrompts=File Name
|
||||||
|
|
||||||
#view pdf
|
#view pdf
|
||||||
viewPdf.title=檢視 PDF
|
viewPdf.title=檢視 PDF
|
||||||
@@ -982,7 +983,7 @@ pdfToImage.color=顏色
|
|||||||
pdfToImage.grey=灰度
|
pdfToImage.grey=灰度
|
||||||
pdfToImage.blackwhite=黑白(可能會遺失資料!)
|
pdfToImage.blackwhite=黑白(可能會遺失資料!)
|
||||||
pdfToImage.submit=轉換
|
pdfToImage.submit=轉換
|
||||||
pdfToImage.info=尚未安裝 Python。需要安裝 Python 才能進行 WebP 轉換。
|
pdfToImage.info=Python is not installed. Required for WebP conversion.
|
||||||
|
|
||||||
|
|
||||||
#addPassword
|
#addPassword
|
||||||
@@ -1019,7 +1020,7 @@ watermark.selectText.6=heightSpacer(每個浮水印之間的垂直間距):
|
|||||||
watermark.selectText.7=不透明度(0% - 100%):
|
watermark.selectText.7=不透明度(0% - 100%):
|
||||||
watermark.selectText.8=浮水印類型:
|
watermark.selectText.8=浮水印類型:
|
||||||
watermark.selectText.9=浮水印影像:
|
watermark.selectText.9=浮水印影像:
|
||||||
watermark.selectText.10=將 PDF 轉換為 PDF 影像
|
watermark.selectText.10=Convert PDF to PDF-Image
|
||||||
watermark.submit=新增浮水印
|
watermark.submit=新增浮水印
|
||||||
watermark.type.1=文字
|
watermark.type.1=文字
|
||||||
watermark.type.2=圖片
|
watermark.type.2=圖片
|
||||||
@@ -1170,55 +1171,55 @@ printFile.submit=列印
|
|||||||
|
|
||||||
|
|
||||||
#licenses
|
#licenses
|
||||||
licenses.nav=授權條款
|
licenses.nav=許可證
|
||||||
licenses.title=第三方授權條款
|
licenses.title=第三方許可證
|
||||||
licenses.header=第三方授權條款
|
licenses.header=第三方許可證
|
||||||
licenses.module=模組
|
licenses.module=模組
|
||||||
licenses.version=版本
|
licenses.version=版本
|
||||||
licenses.license=授權條款
|
licenses.license=許可證
|
||||||
|
|
||||||
#survey
|
#survey
|
||||||
survey.nav=問卷調查
|
survey.nav=問卷調查
|
||||||
survey.title=Stirling-PDF 問卷調查
|
survey.title=Stirling-PDF 問卷調查
|
||||||
survey.description=Stirling-PDF 沒有追蹤功能,因此我們希望聽取使用者的意見來改進 Stirling-PDF!
|
survey.description=Stirling-PDF 沒有追蹤功能,所以我們希望聽取用戶的意見來改進 Stirling-PDF!
|
||||||
survey.changes=Stirling-PDF 自上次調查以來已有所改變!欲了解更多資訊,請查看我們的部落格文章:
|
survey.changes=Stirling-PDF has changed since the last survey! To find out more please check our blog post here:
|
||||||
survey.changes2=隨著這些變更,我們正在獲得付費的商業支援和資金
|
survey.changes2=With these changes we are getting paid business support and funding
|
||||||
survey.please=請考慮參與我們的問卷調查!
|
survey.please=請考慮參加我們的問卷調查!
|
||||||
survey.disabled=(問卷調查彈出視窗將在後續更新中停用,但仍可在頁尾使用)
|
survey.disabled=(問卷調查彈出窗口將在後續更新中停用,但仍可在頁腳處使用)
|
||||||
survey.button=參與問卷調查
|
survey.button=參加問卷調查
|
||||||
survey.dontShowAgain=不要再次顯示
|
survey.dontShowAgain=不要再次顯示
|
||||||
|
|
||||||
|
|
||||||
#error
|
#error
|
||||||
error.sorry=很抱歉造成您的困擾!
|
error.sorry=對於這個問題,我們感到抱歉!
|
||||||
error.needHelp=需要協助或發現問題?
|
error.needHelp=需要幫助/發現了一個問題?
|
||||||
error.contactTip=如果您仍然遇到問題,請不要猶豫,隨時向我們尋求協助。您可以在我們的 GitHub 頁面回報問題,或透過 Discord 跟我們聯絡:
|
error.contactTip=如果你仍然遇到問題,請不要猶豫,隨時向我們尋求幫助。你可以在我們的 GitHub 頁面提交工單,或通過 Discord 與我們聯繋:
|
||||||
error.404.head=404 - 找不到頁面 | 糟糕,我們在程式碼中迷路了!
|
error.404.head=404 - 找不到頁面 | 哎呀,我們在代碼中走錯了路!
|
||||||
error.404.1=我們似乎找不到您正在尋找的頁面。
|
error.404.1=我們好像找不到你正在尋找的頁面。
|
||||||
error.404.2=發生了一些錯誤
|
error.404.2=出了點錯誤
|
||||||
error.github=在 GitHub 上回報問題
|
error.github=在 GitHub 上提交工單
|
||||||
error.showStack=顯示堆疊追蹤
|
error.showStack=顯示堆疊追蹤
|
||||||
error.copyStack=複製堆疊追蹤
|
error.copyStack=複製堆疊追蹤
|
||||||
error.githubSubmit=GitHub - 回報問題
|
error.githubSubmit=GitHub - 提交工單
|
||||||
error.discordSubmit=Discord - 發表支援文章
|
error.discordSubmit=Discord - 提交支援帖子
|
||||||
|
|
||||||
|
|
||||||
#remove-image
|
#remove-image
|
||||||
removeImage.title=移除圖片
|
removeImage.title=Remove image
|
||||||
removeImage.header=移除圖片
|
removeImage.header=Remove image
|
||||||
removeImage.removeImage=移除圖片
|
removeImage.removeImage=Remove image
|
||||||
removeImage.submit=移除圖片
|
removeImage.submit=Remove image
|
||||||
|
|
||||||
|
|
||||||
splitByChapters.title=依章節分割 PDF
|
splitByChapters.title=Split PDF by Chapters
|
||||||
splitByChapters.header=依章節分割 PDF
|
splitByChapters.header=Split PDF by Chapters
|
||||||
splitByChapters.bookmarkLevel=書籤層級
|
splitByChapters.bookmarkLevel=Bookmark Level
|
||||||
splitByChapters.includeMetadata=包含中繼資料
|
splitByChapters.includeMetadata=Include Metadata
|
||||||
splitByChapters.allowDuplicates=允許重複
|
splitByChapters.allowDuplicates=Allow Duplicates
|
||||||
splitByChapters.desc.1=此工具會根據 PDF 檔案的章節結構將其分割成多個 PDF。
|
splitByChapters.desc.1=This tool splits a PDF file into multiple PDFs based on its chapter structure.
|
||||||
splitByChapters.desc.2=書籤層級:選擇用於分割的書籤層級(0 表示最上層,1 表示第二層,依此類推)。
|
splitByChapters.desc.2=Bookmark Level: Choose the level of bookmarks to use for splitting (0 for top-level, 1 for second-level, etc.).
|
||||||
splitByChapters.desc.3=包含中繼資料:如果勾選,原始 PDF 的中繼資料將包含在每個分割後的 PDF 中。
|
splitByChapters.desc.3=Include Metadata: If checked, the original PDF's metadata will be included in each split PDF.
|
||||||
splitByChapters.desc.4=允許重複:如果勾選,允許同一頁面上的多個書籤建立獨立的 PDF。
|
splitByChapters.desc.4=Allow Duplicates: If checked, allows multiple bookmarks on the same page to create separate PDFs.
|
||||||
splitByChapters.submit=分割 PDF
|
splitByChapters.submit=Split PDF
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -47,31 +47,20 @@ security:
|
|||||||
useAsUsername: email # Default is 'email'; custom fields can be used as the username
|
useAsUsername: email # Default is 'email'; custom fields can be used as the username
|
||||||
scopes: openid, profile, email # Specify the scopes for which the application will request permissions
|
scopes: openid, profile, email # Specify the scopes for which the application will request permissions
|
||||||
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
provider: google # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
|
||||||
saml2:
|
|
||||||
enabled: false # Currently in alpha, not recommended for use yet, enableAlphaFunctionality must be set to true
|
|
||||||
autoCreateUser: false # set to 'true' to allow auto-creation of non-existing users
|
|
||||||
blockRegistration: false # set to 'true' to deny login with SSO without prior registration by an admin
|
|
||||||
registrationId: stirling
|
|
||||||
idpMetadataUri: https://dev-XXXXXXXX.okta.com/app/externalKey/sso/saml/metadata
|
|
||||||
idpSingleLogoutUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/slo/saml
|
|
||||||
idpSingleLoginUrl: https://dev-XXXXXXXX.okta.com/app/dev-XXXXXXXX_stirlingpdf_1/externalKey/sso/saml
|
|
||||||
idpIssuer: http://www.okta.com/externalKey
|
|
||||||
idpCert: classpath:octa.crt
|
|
||||||
privateKey: classpath:saml-private-key.key
|
|
||||||
spCert: classpath:saml-public-cert.crt
|
|
||||||
|
|
||||||
|
# Enterprise edition settings unused for now please ignore!
|
||||||
enterpriseEdition:
|
enterpriseEdition:
|
||||||
enabled: false # set to 'true' to enable enterprise edition
|
enabled: false # set to 'true' to enable enterprise edition
|
||||||
key: 00000000-0000-0000-0000-000000000000
|
key: 00000000-0000-0000-0000-000000000000
|
||||||
CustomMetadata:
|
CustomMetadata:
|
||||||
autoUpdateMetadata: false # set to 'true' to automatically update metadata with below values
|
autoUpdateMetadata: true # set to 'true' to automatically update metadata with below values
|
||||||
author: username # Supports text such as 'John Doe' or types such as username to autopopulate with users username
|
author: username # Supports text such as 'John Doe' or types such as username
|
||||||
creator: Stirling-PDF # Supports text such as 'Company-PDF'
|
creator: Stirling-PDF # Supports text such as 'Company-PDF'
|
||||||
producer: Stirling-PDF # Supports text such as 'Company-PDF'
|
producer: Stirling-PDF # Supports text such as 'Company-PDF'
|
||||||
|
|
||||||
legal:
|
legal:
|
||||||
termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms) Empty string to disable or filename to load from local file in static folder
|
termsAndConditions: '' # URL to the terms and conditions of your application (e.g. https://example.com/terms) Empty string to disable or filename to load from local file in static folder
|
||||||
privacyPolicy: https://www.stirlingpdf.com/privacy-policy # URL to the privacy policy of your application (e.g. https://example.com/privacy) Empty string to disable or filename to load from local file in static folder
|
privacyPolicy: '' # URL to the privacy policy of your application (e.g. https://example.com/privacy) Empty string to disable or filename to load from local file in static folder
|
||||||
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility) Empty string to disable or filename to load from local file in static folder
|
accessibilityStatement: '' # URL to the accessibility statement of your application (e.g. https://example.com/accessibility) Empty string to disable or filename to load from local file in static folder
|
||||||
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie) Empty string to disable or filename to load from local file in static folder
|
cookiePolicy: '' # URL to the cookie policy of your application (e.g. https://example.com/cookie) Empty string to disable or filename to load from local file in static folder
|
||||||
impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum) Empty string to disable or filename to load from local file in static folder
|
impressum: '' # URL to the impressum of your application (e.g. https://example.com/impressum) Empty string to disable or filename to load from local file in static folder
|
||||||
|
|||||||
@@ -21,13 +21,6 @@
|
|||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "com.coveo:saml-client",
|
|
||||||
"moduleUrl": "https://github.com/coveo/saml-client",
|
|
||||||
"moduleVersion": "5.0.0",
|
|
||||||
"moduleLicense": "MIT",
|
|
||||||
"moduleLicenseUrl": "https://opensource.org/licenses/MIT"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "com.fasterxml.jackson.core:jackson-annotations",
|
"moduleName": "com.fasterxml.jackson.core:jackson-annotations",
|
||||||
"moduleUrl": "https://github.com/FasterXML/jackson",
|
"moduleUrl": "https://github.com/FasterXML/jackson",
|
||||||
@@ -70,27 +63,6 @@
|
|||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "com.fasterxml.jackson.jaxrs:jackson-jaxrs-base",
|
|
||||||
"moduleUrl": "https://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-base",
|
|
||||||
"moduleVersion": "2.17.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider",
|
|
||||||
"moduleUrl": "https://github.com/FasterXML/jackson-jaxrs-providers/jackson-jaxrs-json-provider",
|
|
||||||
"moduleVersion": "2.17.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.fasterxml.jackson.module:jackson-module-jaxb-annotations",
|
|
||||||
"moduleUrl": "https://github.com/FasterXML/jackson-modules-base",
|
|
||||||
"moduleVersion": "2.17.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "com.fasterxml.jackson.module:jackson-module-parameter-names",
|
"moduleName": "com.fasterxml.jackson.module:jackson-module-parameter-names",
|
||||||
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names",
|
"moduleUrl": "https://github.com/FasterXML/jackson-modules-java8/jackson-module-parameter-names",
|
||||||
@@ -139,46 +111,6 @@
|
|||||||
"moduleLicense": "Apache License, Version 2.0",
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "com.google.code.findbugs:jsr305",
|
|
||||||
"moduleUrl": "http://findbugs.sourceforge.net/",
|
|
||||||
"moduleVersion": "3.0.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.google.errorprone:error_prone_annotations",
|
|
||||||
"moduleVersion": "2.11.0",
|
|
||||||
"moduleLicense": "Apache 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.google.guava:failureaccess",
|
|
||||||
"moduleUrl": "https://github.com/google/guava/",
|
|
||||||
"moduleVersion": "1.0.1",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.google.guava:guava",
|
|
||||||
"moduleUrl": "https://github.com/google/guava/",
|
|
||||||
"moduleVersion": "31.1-jre",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.google.guava:listenablefuture",
|
|
||||||
"moduleVersion": "9999.0-empty-to-avoid-conflict-with-guava",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.google.j2objc:j2objc-annotations",
|
|
||||||
"moduleUrl": "https://github.com/google/j2objc/",
|
|
||||||
"moduleVersion": "1.3",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "com.google.zxing:core",
|
"moduleName": "com.google.zxing:core",
|
||||||
"moduleUrl": "https://github.com/zxing/zxing/core",
|
"moduleUrl": "https://github.com/zxing/zxing/core",
|
||||||
@@ -186,25 +118,6 @@
|
|||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software 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": "com.googlecode.owasp-java-html-sanitizer:java10-shim",
|
|
||||||
"moduleVersion": "20240325.1",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.googlecode.owasp-java-html-sanitizer:java8-shim",
|
|
||||||
"moduleVersion": "20240325.1",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.googlecode.owasp-java-html-sanitizer:owasp-java-html-sanitizer",
|
|
||||||
"moduleUrl": "https://owasp.org",
|
|
||||||
"moduleVersion": "20240325.1",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "com.h2database:h2",
|
"moduleName": "com.h2database:h2",
|
||||||
"moduleUrl": "https://h2database.com",
|
"moduleUrl": "https://h2database.com",
|
||||||
@@ -254,13 +167,6 @@
|
|||||||
"moduleLicense": "Apache 2",
|
"moduleLicense": "Apache 2",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "com.posthog.java:posthog",
|
|
||||||
"moduleUrl": "http://github.com/PostHog/posthog-java",
|
|
||||||
"moduleVersion": "1.1.1",
|
|
||||||
"moduleLicense": "MIT License",
|
|
||||||
"moduleLicenseUrl": "http://www.opensource.org/licenses/mit-license.php"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "com.sun.istack:istack-commons-runtime",
|
"moduleName": "com.sun.istack:istack-commons-runtime",
|
||||||
"moduleUrl": "https://www.eclipse.org",
|
"moduleUrl": "https://www.eclipse.org",
|
||||||
@@ -270,78 +176,64 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.common:common-image",
|
"moduleName": "com.twelvemonkeys.common:common-image",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.common:common-io",
|
"moduleName": "com.twelvemonkeys.common:common-io",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.common:common-lang",
|
"moduleName": "com.twelvemonkeys.common:common-lang",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.imageio:imageio-batik",
|
"moduleName": "com.twelvemonkeys.imageio:imageio-batik",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.imageio:imageio-bmp",
|
"moduleName": "com.twelvemonkeys.imageio:imageio-bmp",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.imageio:imageio-core",
|
"moduleName": "com.twelvemonkeys.imageio:imageio-core",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.imageio:imageio-jpeg",
|
"moduleName": "com.twelvemonkeys.imageio:imageio-jpeg",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.imageio:imageio-metadata",
|
"moduleName": "com.twelvemonkeys.imageio:imageio-metadata",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.imageio:imageio-tiff",
|
"moduleName": "com.twelvemonkeys.imageio:imageio-tiff",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "com.twelvemonkeys.imageio:imageio-webp",
|
"moduleName": "com.twelvemonkeys.imageio:imageio-webp",
|
||||||
"moduleVersion": "3.12.0",
|
"moduleVersion": "3.11.0",
|
||||||
"moduleLicense": "The BSD License",
|
"moduleLicense": "The BSD License",
|
||||||
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
"moduleLicenseUrl": "https://github.com/haraldk/TwelveMonkeys#license"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "com.unboundid.product.scim2:scim2-sdk-client",
|
|
||||||
"moduleUrl": "https://github.com/pingidentity/scim2",
|
|
||||||
"moduleVersion": "2.3.5",
|
|
||||||
"moduleLicense": "UnboundID SCIM2 SDK Free Use License",
|
|
||||||
"moduleLicenseUrl": "https://github.com/pingidentity/scim2"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "com.unboundid.product.scim2:scim2-sdk-common",
|
|
||||||
"moduleUrl": "https://github.com/pingidentity/scim2",
|
|
||||||
"moduleVersion": "2.3.5",
|
|
||||||
"moduleLicense": "UnboundID SCIM2 SDK Free Use License",
|
|
||||||
"moduleLicenseUrl": "https://github.com/pingidentity/scim2"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "com.zaxxer:HikariCP",
|
"moduleName": "com.zaxxer:HikariCP",
|
||||||
"moduleUrl": "https://github.com/brettwooldridge/HikariCP",
|
"moduleUrl": "https://github.com/brettwooldridge/HikariCP",
|
||||||
@@ -356,13 +248,6 @@
|
|||||||
"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": "commons-codec:commons-codec",
|
|
||||||
"moduleUrl": "https://commons.apache.org/proper/commons-codec/",
|
|
||||||
"moduleVersion": "1.16.1",
|
|
||||||
"moduleLicense": "Apache-2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "commons-collections:commons-collections",
|
"moduleName": "commons-collections:commons-collections",
|
||||||
"moduleUrl": "http://commons.apache.org/collections/",
|
"moduleUrl": "http://commons.apache.org/collections/",
|
||||||
@@ -384,12 +269,6 @@
|
|||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
"moduleLicenseUrl": "/LICENSE.txt"
|
"moduleLicenseUrl": "/LICENSE.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "io.dropwizard.metrics:metrics-core",
|
|
||||||
"moduleVersion": "4.2.25",
|
|
||||||
"moduleLicense": "Apache License 2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.html"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "io.github.pixee:java-security-toolkit",
|
"moduleName": "io.github.pixee:java-security-toolkit",
|
||||||
"moduleUrl": "https://github.com/pixee/java-security-toolkit",
|
"moduleUrl": "https://github.com/pixee/java-security-toolkit",
|
||||||
@@ -407,7 +286,7 @@
|
|||||||
{
|
{
|
||||||
"moduleName": "io.micrometer:micrometer-core",
|
"moduleName": "io.micrometer:micrometer-core",
|
||||||
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
|
"moduleUrl": "https://github.com/micrometer-metrics/micrometer",
|
||||||
"moduleVersion": "1.13.6",
|
"moduleVersion": "1.13.4",
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
@@ -543,20 +422,6 @@
|
|||||||
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
"moduleLicense": "GNU General Public License, version 2 with the GNU Classpath Exception",
|
||||||
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
"moduleLicenseUrl": "https://www.gnu.org/software/classpath/license.html"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "javax.activation:javax.activation-api",
|
|
||||||
"moduleUrl": "http://www.oracle.com",
|
|
||||||
"moduleVersion": "1.2.0",
|
|
||||||
"moduleLicense": "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0",
|
|
||||||
"moduleLicenseUrl": "https://opensource.org/licenses/CDDL-1.0"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "javax.xml.bind:jaxb-api",
|
|
||||||
"moduleUrl": "http://www.oracle.com/",
|
|
||||||
"moduleVersion": "2.3.1",
|
|
||||||
"moduleLicense": "GPL2 w/ CPE",
|
|
||||||
"moduleLicenseUrl": "https://oss.oracle.com/licenses/CDDL+GPL-1.1"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "net.bytebuddy:byte-buddy",
|
"moduleName": "net.bytebuddy:byte-buddy",
|
||||||
"moduleVersion": "1.14.19",
|
"moduleVersion": "1.14.19",
|
||||||
@@ -577,12 +442,6 @@
|
|||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "net.shibboleth.utilities:java-support",
|
|
||||||
"moduleVersion": "8.4.2",
|
|
||||||
"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/",
|
||||||
@@ -611,20 +470,6 @@
|
|||||||
"moduleLicense": "Apache-2.0",
|
"moduleLicense": "Apache-2.0",
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "org.apache.httpcomponents:httpclient",
|
|
||||||
"moduleUrl": "http://hc.apache.org/httpcomponents-client-ga",
|
|
||||||
"moduleVersion": "4.5.14",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.apache.httpcomponents:httpcore",
|
|
||||||
"moduleUrl": "http://hc.apache.org/httpcomponents-core-ga",
|
|
||||||
"moduleVersion": "4.4.16",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.apache.logging.log4j:log4j-api",
|
"moduleName": "org.apache.logging.log4j:log4j-api",
|
||||||
"moduleVersion": "2.23.1",
|
"moduleVersion": "2.23.1",
|
||||||
@@ -671,13 +516,6 @@
|
|||||||
"moduleLicense": "Apache-2.0",
|
"moduleLicense": "Apache-2.0",
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "org.apache.santuario:xmlsec",
|
|
||||||
"moduleUrl": "https://www.apache.org/",
|
|
||||||
"moduleVersion": "2.3.4",
|
|
||||||
"moduleLicense": "Apache-2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.apache.tomcat.embed:tomcat-embed-el",
|
"moduleName": "org.apache.tomcat.embed:tomcat-embed-el",
|
||||||
"moduleUrl": "https://tomcat.apache.org/",
|
"moduleUrl": "https://tomcat.apache.org/",
|
||||||
@@ -685,23 +523,16 @@
|
|||||||
"moduleLicense": "Apache License, Version 2.0",
|
"moduleLicense": "Apache License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "org.apache.velocity:velocity-engine-core",
|
|
||||||
"moduleUrl": "https://www.apache.org/",
|
|
||||||
"moduleVersion": "2.3",
|
|
||||||
"moduleLicense": "Apache License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "https://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.apache.xmlgraphics:batik-all",
|
"moduleName": "org.apache.xmlgraphics:batik-all",
|
||||||
"moduleVersion": "1.18",
|
"moduleVersion": "1.17",
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "org.apache.xmlgraphics:xmlgraphics-commons",
|
"moduleName": "org.apache.xmlgraphics:xmlgraphics-commons",
|
||||||
"moduleUrl": "http://xmlgraphics.apache.org/commons/",
|
"moduleUrl": "http://xmlgraphics.apache.org/commons/",
|
||||||
"moduleVersion": "2.10",
|
"moduleVersion": "2.9",
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
"moduleLicense": "The Apache Software License, Version 2.0",
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
||||||
},
|
},
|
||||||
@@ -740,32 +571,18 @@
|
|||||||
"moduleLicense": "Bouncy Castle Licence",
|
"moduleLicense": "Bouncy Castle Licence",
|
||||||
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
|
"moduleLicenseUrl": "https://www.bouncycastle.org/licence.html"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "org.checkerframework:checker-qual",
|
|
||||||
"moduleUrl": "https://checkerframework.org",
|
|
||||||
"moduleVersion": "3.12.0",
|
|
||||||
"moduleLicense": "The MIT License",
|
|
||||||
"moduleLicenseUrl": "http://opensource.org/licenses/MIT"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.commonmark:commonmark",
|
"moduleName": "org.commonmark:commonmark",
|
||||||
"moduleVersion": "0.24.0",
|
"moduleVersion": "0.23.0",
|
||||||
"moduleLicense": "BSD-2-Clause",
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause"
|
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"moduleName": "org.commonmark:commonmark-ext-gfm-tables",
|
"moduleName": "org.commonmark:commonmark-ext-gfm-tables",
|
||||||
"moduleVersion": "0.24.0",
|
"moduleVersion": "0.23.0",
|
||||||
"moduleLicense": "BSD-2-Clause",
|
"moduleLicense": "BSD 2-Clause License",
|
||||||
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause"
|
"moduleLicenseUrl": "https://opensource.org/licenses/BSD-2-Clause"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"moduleName": "org.cryptacular:cryptacular",
|
|
||||||
"moduleUrl": "http://www.cryptacular.org",
|
|
||||||
"moduleVersion": "1.2.5",
|
|
||||||
"moduleLicense": "GNU Lesser General Public License",
|
|
||||||
"moduleLicenseUrl": "http://www.gnu.org/licenses/lgpl-3.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.eclipse.angus:angus-activation",
|
"moduleName": "org.eclipse.angus:angus-activation",
|
||||||
"moduleUrl": "https://www.eclipse.org",
|
"moduleUrl": "https://www.eclipse.org",
|
||||||
@@ -1011,78 +828,6 @@
|
|||||||
"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.opensaml:opensaml-core",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-messaging-api",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-profile-api",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-saml-api",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-saml-impl",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-security-api",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-security-impl",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-soap-api",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-soap-impl",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-storage-api",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-xmlsec-api",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"moduleName": "org.opensaml:opensaml-xmlsec-impl",
|
|
||||||
"moduleVersion": "4.3.2",
|
|
||||||
"moduleLicense": "The Apache Software License, Version 2.0",
|
|
||||||
"moduleLicenseUrl": "http://www.apache.org/licenses/LICENSE-2.0.txt"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"moduleName": "org.ow2.asm:asm",
|
"moduleName": "org.ow2.asm:asm",
|
||||||
"moduleUrl": "http://asm.ow2.org",
|
"moduleUrl": "http://asm.ow2.org",
|
||||||
@@ -1311,13 +1056,6 @@
|
|||||||
"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-saml2-service-provider",
|
|
||||||
"moduleUrl": "https://spring.io/projects/spring-security",
|
|
||||||
"moduleVersion": "6.3.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",
|
||||||
@@ -1405,7 +1143,7 @@
|
|||||||
{
|
{
|
||||||
"moduleName": "org.springframework:spring-webmvc",
|
"moduleName": "org.springframework:spring-webmvc",
|
||||||
"moduleUrl": "https://github.com/spring-projects/spring-framework",
|
"moduleUrl": "https://github.com/spring-projects/spring-framework",
|
||||||
"moduleVersion": "6.1.14",
|
"moduleVersion": "6.1.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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
select#font-select,
|
select#font-select,
|
||||||
select#font-select option {
|
select#font-select option {
|
||||||
height: 60px;
|
height: 60px; /* Adjust as needed */
|
||||||
/* Adjust as needed */
|
font-size: 30px; /* Adjust as needed */
|
||||||
font-size: 30px;
|
|
||||||
/* Adjust as needed */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.drawing-pad-container {
|
.drawing-pad-container {
|
||||||
@@ -15,12 +13,10 @@ select#font-select option {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#box-drag-container {
|
#box-drag-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggable-buttons-box {
|
.draggable-buttons-box {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -28,37 +24,16 @@ select#font-select option {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 5px;
|
gap: 5px;
|
||||||
z-index: 5;
|
|
||||||
}
|
}
|
||||||
|
.draggable-buttons-box > button {
|
||||||
.draggable-buttons-box>button {
|
z-index: 10;
|
||||||
z-index: 4;
|
|
||||||
background-color: rgba(13, 110, 253, 0.1);
|
background-color: rgba(13, 110, 253, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.draggable-canvas {
|
.draggable-canvas {
|
||||||
border: 2px solid #3498db;
|
border: 1px solid red;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
touch-action: none;
|
touch-action: none;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
top: 0px;
|
top: 0px;
|
||||||
left: 0;
|
left: 0;
|
||||||
z-index: 100;
|
|
||||||
cursor: grab;
|
|
||||||
transition: transform 0.1s ease-out;
|
|
||||||
background-color: rgba(52, 152, 219, 0.1);
|
|
||||||
/* Light blue background */
|
|
||||||
}
|
|
||||||
|
|
||||||
.draggable-canvas:active {
|
|
||||||
cursor: grabbing;
|
|
||||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
||||||
/* Shadow on active drag */
|
|
||||||
}
|
|
||||||
|
|
||||||
.draggable-canvas:hover {
|
|
||||||
border: 2px solid #2980b9;
|
|
||||||
/* Darker border on hover */
|
|
||||||
background-color: rgba(52, 152, 219, 0.2);
|
|
||||||
/* Darken background on hover */
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
const draggableElement = document.querySelector('.draggable-canvas');
|
|
||||||
|
|
||||||
// Variables to store the current position of the draggable element
|
|
||||||
let offsetX, offsetY, isDragging = false;
|
|
||||||
|
|
||||||
draggableElement.addEventListener('mousedown', (e) => {
|
|
||||||
// Get the offset when the mouse is clicked inside the element
|
|
||||||
offsetX = e.clientX - draggableElement.getBoundingClientRect().left;
|
|
||||||
offsetY = e.clientY - draggableElement.getBoundingClientRect().top;
|
|
||||||
|
|
||||||
// Set isDragging to true
|
|
||||||
isDragging = true;
|
|
||||||
|
|
||||||
// Add event listeners for mouse movement and release
|
|
||||||
document.addEventListener('mousemove', onMouseMove);
|
|
||||||
document.addEventListener('mouseup', onMouseUp);
|
|
||||||
});
|
|
||||||
|
|
||||||
function onMouseMove(e) {
|
|
||||||
if (isDragging) {
|
|
||||||
// Calculate the new position of the element
|
|
||||||
const left = e.clientX - offsetX;
|
|
||||||
const top = e.clientY - offsetY;
|
|
||||||
|
|
||||||
// Move the element by setting its style
|
|
||||||
draggableElement.style.left = `${left}px`;
|
|
||||||
draggableElement.style.top = `${top}px`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function onMouseUp() {
|
|
||||||
// Stop dragging and remove event listeners
|
|
||||||
isDragging = false;
|
|
||||||
document.removeEventListener('mousemove', onMouseMove);
|
|
||||||
document.removeEventListener('mouseup', onMouseUp);
|
|
||||||
}
|
|
||||||
39
src/main/resources/static/js/multitool/horizontalScroll.js
Normal file
39
src/main/resources/static/js/multitool/horizontalScroll.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
const scrollDivHorizontally = (id) => {
|
||||||
|
var scrollDeltaX = 0; // variable to store the accumulated horizontal scroll delta
|
||||||
|
var scrollDeltaY = 0; // variable to store the accumulated vertical scroll delta
|
||||||
|
var isScrolling = false; // variable to track if scroll is already in progress
|
||||||
|
const divToScroll = document.getElementById(id);
|
||||||
|
|
||||||
|
function scrollLoop() {
|
||||||
|
// Scroll the div horizontally and vertically by a fraction of the accumulated scroll delta
|
||||||
|
divToScroll.scrollLeft += scrollDeltaX * 0.1;
|
||||||
|
divToScroll.scrollTop += scrollDeltaY * 0.1;
|
||||||
|
|
||||||
|
// Reduce the accumulated scroll delta by a fraction
|
||||||
|
scrollDeltaX *= 0.9;
|
||||||
|
scrollDeltaY *= 0.9;
|
||||||
|
|
||||||
|
// If scroll delta is still significant, continue the scroll loop
|
||||||
|
if (Math.abs(scrollDeltaX) > 0.1 || Math.abs(scrollDeltaY) > 0.1) {
|
||||||
|
requestAnimationFrame(scrollLoop);
|
||||||
|
} else {
|
||||||
|
isScrolling = false; // Reset scroll in progress flag
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
divToScroll.addEventListener("wheel", function (e) {
|
||||||
|
e.preventDefault(); // prevent default mousewheel behavior
|
||||||
|
|
||||||
|
// Accumulate the horizontal and vertical scroll delta
|
||||||
|
scrollDeltaX -= e.deltaX || e.wheelDeltaX || -e.deltaY || -e.wheelDeltaY;
|
||||||
|
scrollDeltaY -= e.deltaY || e.wheelDeltaY || -e.deltaX || -e.wheelDeltaX;
|
||||||
|
|
||||||
|
// If scroll is not already in progress, start the scroll loop
|
||||||
|
if (!isScrolling) {
|
||||||
|
isScrolling = true;
|
||||||
|
requestAnimationFrame(scrollLoop);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export default scrollDivHorizontally;
|
||||||
@@ -283,7 +283,7 @@
|
|||||||
</script>
|
</script>
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
</div>
|
</div>
|
||||||
<div th:if="${altLogin}" class="modal fade" id="editUserModal" tabindex="-1" role="dialog" aria-labelledby="editUserModalLabel" aria-hidden="true">
|
<div th:if="${oAuth2Enabled}" class="modal fade" id="editUserModal" tabindex="-1" role="dialog" aria-labelledby="editUserModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
|
|||||||
@@ -360,7 +360,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li class="nav-item" th:if="${!@runningEE}">
|
<li class="nav-item">
|
||||||
<a href="https://stirlingpdf.com/pricing" class="nav-link go-pro-link" target="_blank" rel="noopener noreferrer">
|
<a href="https://stirlingpdf.com/pricing" class="nav-link go-pro-link" target="_blank" rel="noopener noreferrer">
|
||||||
<span class="go-pro-badge" th:text="#{enterpriseEdition.button}"></span>
|
<span class="go-pro-badge" th:text="#{enterpriseEdition.button}"></span>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -374,10 +374,10 @@
|
|||||||
<p th:text="#{analytics.paragraph2}">Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.</p>
|
<p th:text="#{analytics.paragraph2}">Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better.</p>
|
||||||
<p th:text="#{analytics.settings}">You can change the settings for analytics in the config/settings.yml file</p>
|
<p th:text="#{analytics.settings}">You can change the settings for analytics in the config/settings.yml file</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer justify-content-between">
|
<div class="modal-footer">
|
||||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="setAnalytics(false)" th:text="#{analytics.disable}">Disable analytics</button>
|
<button type="button" class="btn btn-primary" th:text="#{analytics.enable}" onclick="setAnalytics(true)">Enable analytics</button>
|
||||||
<button type="button" class="btn btn-primary" th:text="#{analytics.enable}" onclick="setAnalytics(true)">Enable analytics</button>
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" onclick="setAnalytics(false)" th:text="#{analytics.disable}">Disable analytics</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -426,60 +426,50 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
const surveyVersion = "2.0";
|
const surveyVersion = "2.0";
|
||||||
const modal = new bootstrap.Modal(document.getElementById('surveyModal'));
|
const modal = new bootstrap.Modal(document.getElementById('surveyModal'));
|
||||||
const dontShowAgain = document.getElementById('dontShowAgain');
|
const dontShowAgain = document.getElementById('dontShowAgain');
|
||||||
const takeSurveyButton = document.getElementById('takeSurvey');
|
const takeSurveyButton = document.getElementById('takeSurvey');
|
||||||
|
|
||||||
const viewThresholds = [5, 10, 15, 22, 30, 50, 75, 100, 150, 200];
|
const viewThresholds = [5, 15, 30, 50, 75, 100, 150, 200];
|
||||||
|
let pageViews = parseInt(localStorage.getItem('pageViews') || '0');
|
||||||
|
|
||||||
// Check if survey version changed and reset page views if it did
|
pageViews++;
|
||||||
const storedVersion = localStorage.getItem('surveyVersion');
|
localStorage.setItem('pageViews', pageViews.toString());
|
||||||
if (storedVersion && storedVersion !== surveyVersion) {
|
|
||||||
localStorage.setItem('pageViews', '0');
|
|
||||||
}
|
|
||||||
|
|
||||||
let pageViews = parseInt(localStorage.getItem('pageViews') || '0');
|
function shouldShowSurvey() {
|
||||||
|
if (localStorage.getItem('dontShowSurvey') === 'true' || localStorage.getItem('surveyTaken') === 'true') {
|
||||||
pageViews++;
|
return false;
|
||||||
localStorage.setItem('pageViews', pageViews.toString());
|
|
||||||
|
|
||||||
function shouldShowSurvey() {
|
|
||||||
if (localStorage.getItem('dontShowSurvey') === 'true' ||
|
|
||||||
localStorage.getItem('surveyTaken') === 'true') {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If survey version changed and we hit a threshold, show the survey
|
if (localStorage.getItem('surveyVersion') !== surveyVersion) {
|
||||||
if (localStorage.getItem('surveyVersion') !== surveyVersion &&
|
return true;
|
||||||
viewThresholds.includes(pageViews)) {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return viewThresholds.includes(pageViews);
|
return viewThresholds.includes(pageViews);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (shouldShowSurvey()) {
|
if (shouldShowSurvey()) {
|
||||||
modal.show();
|
modal.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
dontShowAgain.addEventListener('change', function() {
|
dontShowAgain.addEventListener('change', function() {
|
||||||
if (this.checked) {
|
if (this.checked) {
|
||||||
localStorage.setItem('dontShowSurvey', 'true');
|
localStorage.setItem('dontShowSurvey', 'true');
|
||||||
localStorage.setItem('surveyVersion', surveyVersion);
|
localStorage.setItem('surveyVersion', surveyVersion);
|
||||||
} else {
|
} else {
|
||||||
localStorage.removeItem('dontShowSurvey');
|
localStorage.removeItem('dontShowSurvey');
|
||||||
localStorage.removeItem('surveyVersion');
|
localStorage.removeItem('surveyVersion');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
takeSurveyButton.addEventListener('click', function() {
|
takeSurveyButton.addEventListener('click', function() {
|
||||||
localStorage.setItem('surveyTaken', 'true');
|
localStorage.setItem('surveyTaken', 'true');
|
||||||
localStorage.setItem('surveyVersion', surveyVersion);
|
localStorage.setItem('surveyVersion', surveyVersion);
|
||||||
modal.hide();
|
modal.hide();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -114,7 +114,7 @@
|
|||||||
<img class="my-4" th:src="@{'/favicon.svg'}" alt="favicon" width="144" height="144">
|
<img class="my-4" th:src="@{'/favicon.svg'}" alt="favicon" 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="${altLogin} and (${loginMethod} == 'all' or ${loginMethod} == 'oauth2')">
|
<div th:if="${oAuth2Enabled} and (${loginMethod} == 'all' or ${loginMethod} == 'oauth2')">
|
||||||
<a href="#" class="w-100 btn btn-lg btn-primary" data-bs-toggle="modal" data-bs-target="#loginsModal" th:text="#{login.ssoSignIn}">Login Via SSO</a>
|
<a href="#" class="w-100 btn btn-lg btn-primary" data-bs-toggle="modal" data-bs-target="#loginsModal" th:text="#{login.ssoSignIn}">Login Via SSO</a>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
@@ -168,7 +168,7 @@
|
|||||||
</main>
|
</main>
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
</div>
|
</div>
|
||||||
<div th:if="${altLogin}" class="modal fade" id="loginsModal" tabindex="-1" role="dialog" aria-labelledby="loginsModalLabel" aria-hidden="true">
|
<div th:if="${oAuth2Enabled}" class="modal fade" id="loginsModal" tabindex="-1" role="dialog" aria-labelledby="loginsModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="mb-3" th:each="provider : ${providerlist}">
|
<div class="mb-3" th:each="provider : ${providerlist}">
|
||||||
<a th:href="@{|${provider.key}|}" th:text="${provider.value}" class="w-100 btn btn-lg btn-primary">Login Provider</a>
|
<a th:href="@{|/oauth2/authorization/${provider.key}|}" th:text="${provider.value}" class="w-100 btn btn-lg btn-primary">OpenID Connect</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
|
|||||||
@@ -80,6 +80,7 @@
|
|||||||
<script type="module">
|
<script type="module">
|
||||||
import PdfContainer from './js/multitool/PdfContainer.js';
|
import PdfContainer from './js/multitool/PdfContainer.js';
|
||||||
import DragDropManager from "./js/multitool/DragDropManager.js";
|
import DragDropManager from "./js/multitool/DragDropManager.js";
|
||||||
|
import scrollDivHorizontally from "./js/multitool/horizontalScroll.js";
|
||||||
import ImageHighlighter from "./js/multitool/ImageHighlighter.js";
|
import ImageHighlighter from "./js/multitool/ImageHighlighter.js";
|
||||||
import PdfActionsManager from './js/multitool/PdfActionsManager.js';
|
import PdfActionsManager from './js/multitool/PdfActionsManager.js';
|
||||||
import FileDragManager from './js/multitool/fileInput.js';
|
import FileDragManager from './js/multitool/fileInput.js';
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
const fileDragManager = new FileDragManager();
|
const fileDragManager = new FileDragManager();
|
||||||
|
|
||||||
// Scroll the wrapper horizontally
|
// Scroll the wrapper horizontally
|
||||||
|
scrollDivHorizontally('pages-container-wrapper');
|
||||||
|
|
||||||
// Automatically exposes rotateAll, addFiles and exportPdf to the window for the global buttons.
|
// Automatically exposes rotateAll, addFiles and exportPdf to the window for the global buttons.
|
||||||
const pdfContainer = new PdfContainer(
|
const pdfContainer = new PdfContainer(
|
||||||
@@ -109,4 +111,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}"
|
<html th:lang="${#locale.language}" th:dir="#{language.direction}" th:data-language="${#locale.toString()}" xmlns:th="https://www.thymeleaf.org">
|
||||||
xmlns:th="https://www.thymeleaf.org">
|
<head>
|
||||||
|
|
||||||
<head>
|
|
||||||
<th:block th:insert="~{fragments/common :: head(title=#{sign.title}, header=#{sign.header})}"></th:block>
|
<th:block th:insert="~{fragments/common :: head(title=#{sign.title}, header=#{sign.header})}"></th:block>
|
||||||
<link rel="stylesheet" th:href="@{'/css/sign.css'}">
|
<link rel="stylesheet" th:href="@{'/css/sign.css'}">
|
||||||
|
|
||||||
<th:block th:each="font : ${fonts}">
|
<th:block th:each="font : ${fonts}">
|
||||||
<style th:inline="text">
|
<style th:inline="text">
|
||||||
@font-face {
|
@font-face {
|
||||||
@@ -14,266 +11,268 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
#font-select option[value="[[${font.name}]]"] {
|
#font-select option[value="[[${font.name}]]"] {
|
||||||
font-family: "[[${font.name}]]",
|
font-family: "[[${font.name}]]", cursive;
|
||||||
cursive;
|
}
|
||||||
|
#font-select option[value='/*[[${font.name}]]*/'] {
|
||||||
|
font-family: '/*[[${font.name}]]*/', cursive;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
<script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script>
|
||||||
|
<script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
<script th:src="@{'/js/thirdParty/signature_pad.umd.min.js'}"></script>
|
<body>
|
||||||
<script th:src="@{'/js/thirdParty/interact.min.js'}"></script>
|
<div id="page-container">
|
||||||
</head>
|
<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 bg-card">
|
||||||
|
<div class="tool-header">
|
||||||
|
<span class="material-symbols-rounded tool-header-icon sign">signature</span>
|
||||||
|
<span class="tool-header-text" th:text="#{sign.header}"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<body>
|
<!-- pdf selector -->
|
||||||
<div id="page-container">
|
<div th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}"></div>
|
||||||
<div id="content-wrap">
|
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
||||||
<th:block th:insert="~{fragments/navbar.html :: navbar}"></th:block>
|
<script>
|
||||||
<br><br>
|
let originalFileName = '';
|
||||||
<div class="container">
|
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
||||||
<div class="row justify-content-center">
|
const file = event.target.files[0];
|
||||||
<div class="col-md-6 bg-card">
|
if (file) {
|
||||||
<div class="tool-header">
|
originalFileName = file.name.replace(/\.[^/.]+$/, "");
|
||||||
<span class="material-symbols-rounded tool-header-icon sign">signature</span>
|
const pdfData = await file.arrayBuffer();
|
||||||
<span class="tool-header-text" th:text="#{sign.header}"></span>
|
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs'
|
||||||
</div>
|
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
||||||
|
await DraggableUtils.renderPage(pdfDoc, 0);
|
||||||
|
|
||||||
<!-- pdf selector -->
|
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||||
<div
|
el.style.cssText = '';
|
||||||
th:replace="~{fragments/common :: fileSelector(name='pdf-upload', multipleInputsForSingleRequest=false, disableMultipleFiles=true, accept='application/pdf')}">
|
})
|
||||||
</div>
|
}
|
||||||
<script type="module" th:src="@{'/pdfjs-legacy/pdf.mjs'}"></script>
|
|
||||||
<script>
|
|
||||||
let originalFileName = '';
|
|
||||||
document.querySelector('input[name=pdf-upload]').addEventListener('change', async (event) => {
|
|
||||||
const file = event.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
originalFileName = file.name.replace(/\.[^/.]+$/, "");
|
|
||||||
const pdfData = await file.arrayBuffer();
|
|
||||||
pdfjsLib.GlobalWorkerOptions.workerSrc = './pdfjs-legacy/pdf.worker.mjs';
|
|
||||||
const pdfDoc = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
|
||||||
await DraggableUtils.renderPage(pdfDoc, 0);
|
|
||||||
|
|
||||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
|
||||||
el.style.cssText = '';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
|
||||||
el.style.cssText = "display:none !important";
|
|
||||||
});
|
});
|
||||||
});
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
</script>
|
document.querySelectorAll(".show-on-file-selected").forEach(el => {
|
||||||
|
el.style.cssText = "display:none !important";
|
||||||
<div class="tab-group show-on-file-selected">
|
})
|
||||||
<div class="tab-container" th:title="#{sign.upload}">
|
});
|
||||||
<div
|
</script>
|
||||||
th:replace="~{fragments/common :: fileSelector(name='image-upload', multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}">
|
<div class="tab-group show-on-file-selected">
|
||||||
</div>
|
<div class="tab-container" th:title="#{sign.upload}">
|
||||||
<script>
|
<div th:replace="~{fragments/common :: fileSelector(name='image-upload', multipleInputsForSingleRequest=true, accept='image/*', inputText=#{imgPrompt})}"></div>
|
||||||
const imageUpload = document.querySelector('input[name=image-upload]');
|
<script>
|
||||||
imageUpload.addEventListener('change', e => {
|
const imageUpload = document.querySelector('input[name=image-upload]');
|
||||||
if (!e.target.files) return;
|
imageUpload.addEventListener('change', e => {
|
||||||
for (const imageFile of e.target.files) {
|
if(!e.target.files) {
|
||||||
var reader = new FileReader();
|
return;
|
||||||
reader.readAsDataURL(imageFile);
|
|
||||||
reader.onloadend = function (e) {
|
|
||||||
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="tab-container drawing-pad-container" th:title="#{sign.draw}">
|
|
||||||
<canvas id="drawing-pad-canvas"></canvas>
|
|
||||||
<br>
|
|
||||||
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()"
|
|
||||||
th:text="#{sign.clear}"></button>
|
|
||||||
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()"
|
|
||||||
th:text="#{sign.add}"></button>
|
|
||||||
<script>
|
|
||||||
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
|
|
||||||
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
|
||||||
minWidth: 1,
|
|
||||||
maxWidth: 2,
|
|
||||||
penColor: 'black',
|
|
||||||
});
|
|
||||||
|
|
||||||
function addDraggableFromPad() {
|
|
||||||
if (signaturePad.isEmpty()) return;
|
|
||||||
const startTime = Date.now();
|
|
||||||
const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas);
|
|
||||||
console.log(Date.now() - startTime);
|
|
||||||
DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getCroppedCanvasDataUrl(canvas) {
|
|
||||||
let originalCtx = canvas.getContext('2d');
|
|
||||||
let originalWidth = canvas.width;
|
|
||||||
let originalHeight = canvas.height;
|
|
||||||
let imageData = originalCtx.getImageData(0, 0, originalWidth, originalHeight);
|
|
||||||
|
|
||||||
let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;
|
|
||||||
|
|
||||||
for (y = 0; y < originalHeight; y++) {
|
|
||||||
for (x = 0; x < originalWidth; x++) {
|
|
||||||
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
|
||||||
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
|
|
||||||
if (currentPixelAlphaValue > 0) {
|
|
||||||
if (minX > x) minX = x;
|
|
||||||
if (maxX < x) maxX = x;
|
|
||||||
if (minY > y) minY = y;
|
|
||||||
if (maxY < y) maxY = y;
|
|
||||||
}
|
}
|
||||||
|
for (const imageFile of e.target.files) {
|
||||||
|
var reader = new FileReader();
|
||||||
|
reader.readAsDataURL(imageFile);
|
||||||
|
reader.onloadend = function (e) {
|
||||||
|
DraggableUtils.createDraggableCanvasFromUrl(e.target.result);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
<div class="tab-container drawing-pad-container" th:title="#{sign.draw}">
|
||||||
|
<canvas id="drawing-pad-canvas"></canvas>
|
||||||
|
<br>
|
||||||
|
<button id="clear-signature" class="btn btn-outline-danger mt-2" onclick="signaturePad.clear()" th:text="#{sign.clear}"></button>
|
||||||
|
<button id="save-signature" class="btn btn-outline-success mt-2" onclick="addDraggableFromPad()" th:text="#{sign.add}"></button>
|
||||||
|
<script>
|
||||||
|
const signaturePadCanvas = document.getElementById('drawing-pad-canvas');
|
||||||
|
const signaturePad = new SignaturePad(signaturePadCanvas, {
|
||||||
|
minWidth: 1,
|
||||||
|
maxWidth: 2,
|
||||||
|
penColor: 'black',
|
||||||
|
});
|
||||||
|
function addDraggableFromPad() {
|
||||||
|
if (signaturePad.isEmpty()) return;
|
||||||
|
const startTime = Date.now();
|
||||||
|
const croppedDataUrl = getCroppedCanvasDataUrl(signaturePadCanvas)
|
||||||
|
console.log(Date.now() - startTime);
|
||||||
|
DraggableUtils.createDraggableCanvasFromUrl(croppedDataUrl);
|
||||||
}
|
}
|
||||||
}
|
function getCroppedCanvasDataUrl(canvas) {
|
||||||
|
// code is from: https://github.com/szimek/signature_pad/issues/49#issuecomment-1104035775
|
||||||
|
let originalCtx = canvas.getContext('2d');
|
||||||
|
|
||||||
let croppedWidth = maxX - minX;
|
let originalWidth = canvas.width;
|
||||||
let croppedHeight = maxY - minY;
|
let originalHeight = canvas.height;
|
||||||
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
let imageData = originalCtx.getImageData(0,0, originalWidth, originalHeight);
|
||||||
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
|
||||||
|
|
||||||
let croppedCanvas = document.createElement('canvas'),
|
let minX = originalWidth + 1, maxX = -1, minY = originalHeight + 1, maxY = -1, x = 0, y = 0, currentPixelColorValueIndex;
|
||||||
croppedCtx = croppedCanvas.getContext('2d');
|
|
||||||
|
|
||||||
croppedCanvas.width = croppedWidth;
|
for (y = 0; y < originalHeight; y++) {
|
||||||
croppedCanvas.height = croppedHeight;
|
for (x = 0; x < originalWidth; x++) {
|
||||||
croppedCtx.putImageData(cuttedImageData, 0, 0);
|
currentPixelColorValueIndex = (y * originalWidth + x) * 4;
|
||||||
|
let currentPixelAlphaValue = imageData.data[currentPixelColorValueIndex + 3];
|
||||||
|
if (currentPixelAlphaValue > 0) {
|
||||||
|
if (minX > x) minX = x;
|
||||||
|
if (maxX < x) maxX = x;
|
||||||
|
if (minY > y) minY = y;
|
||||||
|
if (maxY < y) maxY = y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return croppedCanvas.toDataURL();
|
let croppedWidth = maxX - minX;
|
||||||
}
|
let croppedHeight = maxY - minY;
|
||||||
|
if (croppedWidth < 0 || croppedHeight < 0) return null;
|
||||||
|
let cuttedImageData = originalCtx.getImageData(minX, minY, croppedWidth, croppedHeight);
|
||||||
|
|
||||||
function resizeCanvas() {
|
let croppedCanvas = document.createElement('canvas'),
|
||||||
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
croppedCtx = croppedCanvas.getContext('2d');
|
||||||
var additionalFactor = 10;
|
|
||||||
|
|
||||||
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
|
croppedCanvas.width = croppedWidth;
|
||||||
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
|
croppedCanvas.height = croppedHeight;
|
||||||
signaturePadCanvas.getContext("2d").scale(ratio * additionalFactor, ratio * additionalFactor);
|
croppedCtx.putImageData(cuttedImageData, 0, 0);
|
||||||
|
|
||||||
signaturePad.clear();
|
return croppedCanvas.toDataURL();
|
||||||
}
|
}
|
||||||
|
function resizeCanvas() {
|
||||||
|
// When zoomed out to less than 100%, for some very strange reason,
|
||||||
|
// some browsers report devicePixelRatio as less than 1
|
||||||
|
// and only part of the canvas is cleared then.
|
||||||
|
var ratio = Math.max(window.devicePixelRatio || 1, 1);
|
||||||
|
var additionalFactor = 10;
|
||||||
|
|
||||||
new IntersectionObserver((entries, observer) => {
|
signaturePadCanvas.width = signaturePadCanvas.offsetWidth * ratio * additionalFactor;
|
||||||
if (entries.some(entry => entry.intersectionRatio > 0)) {
|
signaturePadCanvas.height = signaturePadCanvas.offsetHeight * ratio * additionalFactor;
|
||||||
resizeCanvas();
|
signaturePadCanvas.getContext("2d").scale(ratio * additionalFactor, ratio * additionalFactor);
|
||||||
}
|
|
||||||
}).observe(signaturePadCanvas);
|
|
||||||
|
|
||||||
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
// This library does not listen for canvas changes, so after the canvas is automatically
|
||||||
</script>
|
// cleared by the browser, SignaturePad#isEmpty might still return false, even though the
|
||||||
</div>
|
// canvas looks empty, because the internal data of this library wasn't cleared. To make sure
|
||||||
|
// that the state of this library is consistent with visual state of the canvas, you
|
||||||
|
// have to clear it manually.
|
||||||
|
signaturePad.clear();
|
||||||
|
}
|
||||||
|
new IntersectionObserver((entries, observer) => {
|
||||||
|
if (entries.some(entry => entry.intersectionRatio > 0)) {
|
||||||
|
resizeCanvas();
|
||||||
|
}
|
||||||
|
}).observe(signaturePadCanvas);
|
||||||
|
new ResizeObserver(resizeCanvas).observe(signaturePadCanvas);
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
<div class="tab-container" th:title="#{sign.text}">
|
||||||
|
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
|
||||||
|
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
|
||||||
|
<label th:text="#{font}"></label>
|
||||||
|
<select class="form-control" name="font" id="font-select">
|
||||||
|
<option th:each="font : ${fonts}" th:value="${font.name}" th:text="${font.name}" th:class="${font.name.toLowerCase()+'-font'}">
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<div class="margin-auto-parent">
|
||||||
|
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center" onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function addDraggableFromText() {
|
||||||
|
const sigText = document.getElementById('sigText').value;
|
||||||
|
const font = document.querySelector('select[name=font]').value;
|
||||||
|
const fontSize = 100;
|
||||||
|
|
||||||
<div class="tab-container" th:title="#{sign.text}">
|
const canvas = document.createElement('canvas');
|
||||||
<label class="form-check-label" for="sigText" th:text="#{text}"></label>
|
const ctx = canvas.getContext('2d');
|
||||||
<textarea class="form-control" id="sigText" name="sigText" rows="3"></textarea>
|
ctx.font = `${fontSize}px ${font}`;
|
||||||
<label th:text="#{font}"></label>
|
const textWidth = ctx.measureText(sigText).width;
|
||||||
<select class="form-control" name="font" id="font-select">
|
const textHeight = fontSize;
|
||||||
<option th:each="font : ${fonts}" th:value="${font.name}" th:text="${font.name}"
|
|
||||||
th:class="${font.name.toLowerCase()+'-font'}"></option>
|
let paragraphs = sigText.split(/\r?\n/);
|
||||||
</select>
|
|
||||||
<div class="margin-auto-parent">
|
canvas.width = textWidth;
|
||||||
<button id="save-text-signature" class="btn btn-outline-success mt-2 margin-center"
|
canvas.height = paragraphs.length * textHeight*1.35; //for tails
|
||||||
onclick="addDraggableFromText()" th:text="#{sign.add}"></button>
|
ctx.font = `${fontSize}px ${font}`;
|
||||||
|
|
||||||
|
ctx.textBaseline = 'top';
|
||||||
|
|
||||||
|
let y = 0;
|
||||||
|
|
||||||
|
paragraphs.forEach(paragraph => {
|
||||||
|
ctx.fillText(paragraph, 0, y);
|
||||||
|
y += fontSize;
|
||||||
|
});
|
||||||
|
|
||||||
|
const dataURL = canvas.toDataURL();
|
||||||
|
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<script>
|
||||||
|
const sigTextInput = document.getElementById('sigText');
|
||||||
|
const fontSelect = document.getElementById('font-select');
|
||||||
|
|
||||||
|
const updateOptionTexts = () => {
|
||||||
|
Array.from(fontSelect.options).forEach(option => {
|
||||||
|
const fontName = option.value.replace(/-regular$/i, '');
|
||||||
|
option.text = sigTextInput.value || fontName;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
sigTextInput.addEventListener('input', updateOptionTexts);
|
||||||
|
|
||||||
|
fontSelect.addEventListener('change', (e) => {
|
||||||
|
e.target.style.fontFamily = e.target.value;
|
||||||
|
updateOptionTexts();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manually trigger the change event
|
||||||
|
fontSelect.dispatchEvent(new Event('change'));
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
<script>
|
|
||||||
function addDraggableFromText() {
|
|
||||||
const sigText = document.getElementById('sigText').value;
|
|
||||||
const font = document.querySelector('select[name=font]').value;
|
|
||||||
const fontSize = 100;
|
|
||||||
|
|
||||||
const canvas = document.createElement('canvas');
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
ctx.font = `${fontSize}px ${font}`;
|
|
||||||
const textWidth = ctx.measureText(sigText).width;
|
|
||||||
const textHeight = fontSize;
|
|
||||||
|
|
||||||
let paragraphs = sigText.split(/\r?\n/);
|
|
||||||
|
|
||||||
canvas.width = textWidth;
|
|
||||||
canvas.height = paragraphs.length * textHeight * 1.35; // for tails
|
|
||||||
ctx.font = `${fontSize}px ${font}`;
|
|
||||||
|
|
||||||
ctx.textBaseline = 'top';
|
|
||||||
|
|
||||||
let y = 0;
|
|
||||||
|
|
||||||
paragraphs.forEach(paragraph => {
|
|
||||||
ctx.fillText(paragraph, 0, y);
|
|
||||||
y += fontSize;
|
|
||||||
});
|
|
||||||
|
|
||||||
const dataURL = canvas.toDataURL();
|
|
||||||
DraggableUtils.createDraggableCanvasFromUrl(dataURL);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- draggables box -->
|
<!-- draggables box -->
|
||||||
<div id="box-drag-container" class="show-on-file-selected">
|
<div id="box-drag-container" class="show-on-file-selected">
|
||||||
<canvas id="pdf-canvas"></canvas>
|
<canvas id="pdf-canvas"></canvas>
|
||||||
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
<script th:src="@{'/js/thirdParty/pdf-lib.min.js'}"></script>
|
||||||
<script th:src="@{'/js/draggable-utils.js'}"></script>
|
<script th:src="@{'/js/draggable-utils.js'}"></script>
|
||||||
<div class="draggable-buttons-box ignore-rtl">
|
<div class="draggable-buttons-box ignore-rtl">
|
||||||
<button class="btn btn-outline-secondary"
|
<button class="btn btn-outline-secondary" onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
|
||||||
onclick="DraggableUtils.deleteDraggableCanvas(DraggableUtils.getLastInteracted())">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash" viewBox="0 0 16 16">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-trash"
|
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/>
|
||||||
viewBox="0 0 16 16">
|
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z"/>
|
||||||
<path
|
</svg>
|
||||||
d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z" />
|
</button>
|
||||||
<path
|
<button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()" style="margin-left:auto">
|
||||||
d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z" />
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-left" viewBox="0 0 16 16">
|
||||||
</svg>
|
<path fill-rule="evenodd" d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z"/>
|
||||||
</button>
|
</svg>
|
||||||
<button class="btn btn-outline-secondary"
|
</button>
|
||||||
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.incrementPage() : DraggableUtils.decrementPage()"
|
<button class="btn btn-outline-secondary" onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()">
|
||||||
style="margin-left:auto">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-right" viewBox="0 0 16 16">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
<path fill-rule="evenodd" d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z"/>
|
||||||
class="bi bi-chevron-left" viewBox="0 0 16 16">
|
</svg>
|
||||||
<path fill-rule="evenodd"
|
</button>
|
||||||
d="M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z" />
|
</div>
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
<button class="btn btn-outline-secondary"
|
|
||||||
onclick="document.documentElement.getAttribute('dir')==='rtl' ? DraggableUtils.decrementPage() : DraggableUtils.incrementPage()">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
|
|
||||||
class="bi bi-chevron-right" viewBox="0 0 16 16">
|
|
||||||
<path fill-rule="evenodd"
|
|
||||||
d="M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z" />
|
|
||||||
</svg>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- download button -->
|
<!-- download button -->
|
||||||
<div class="margin-auto-parent">
|
<div class="margin-auto-parent">
|
||||||
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center"
|
<button id="download-pdf" class="btn btn-primary mb-2 show-on-file-selected margin-center" th:text="#{downloadPdf}"></button>
|
||||||
th:text="#{downloadPdf}"></button>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("download-pdf").addEventListener('click', async () => {
|
document.getElementById("download-pdf").addEventListener('click', async() => {
|
||||||
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
const modifiedPdf = await DraggableUtils.getOverlayedPdfDocument();
|
||||||
const modifiedPdfBytes = await modifiedPdf.save();
|
const modifiedPdfBytes = await modifiedPdf.save();
|
||||||
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
const blob = new Blob([modifiedPdfBytes], { type: 'application/pdf' });
|
||||||
const link = document.createElement('a');
|
const link = document.createElement('a');
|
||||||
link.href = URL.createObjectURL(blob);
|
link.href = URL.createObjectURL(blob);
|
||||||
link.download = originalFileName + '_signed.pdf';
|
link.download = originalFileName + '_signed.pdf';
|
||||||
link.click();
|
link.click();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
||||||
</div>
|
</div>
|
||||||
|
</body>
|
||||||
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Link the draggable.js file -->
|
|
||||||
<script src="/path/to/your/draggable.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Reference in New Issue
Block a user