diff --git a/.github/scripts/check_language_properties.py b/.github/scripts/check_language_properties.py deleted file mode 100644 index a77c378a6..000000000 --- a/.github/scripts/check_language_properties.py +++ /dev/null @@ -1,403 +0,0 @@ -""" -Author: Ludy87 -Description: This script processes .properties files for localization checks. It compares translation files in a branch with -a reference file to ensure consistency. The script performs two main checks: -1. Verifies that the number of lines (including comments and empty lines) in the translation files matches the reference file. -2. Ensures that all keys in the translation files are present in the reference file and vice versa. - -The script also provides functionality to update the translation files to match the reference file by adding missing keys and -adjusting the format. - -Usage: - python check_language_properties.py --reference-file --branch [--actor ] [--files ] -""" -# Sample for Windows: -# python .github/scripts/check_language_properties.py --reference-file src\main\resources\messages_en_GB.properties --branch "" --files src\main\resources\messages_de_DE.properties src\main\resources\messages_uk_UA.properties - -import copy -import glob -import os -import argparse -import re - - -def find_duplicate_keys(file_path): - """ - Identifies duplicate keys in a .properties file. - :param file_path: Path to the .properties file. - :return: List of tuples (key, first_occurrence_line, duplicate_line). - """ - keys = {} - duplicates = [] - - with open(file_path, "r", encoding="utf-8") as file: - for line_number, line in enumerate(file, start=1): - stripped_line = line.strip() - - # Skip empty lines and comments - if not stripped_line or stripped_line.startswith("#"): - continue - - # Split the line into key and value - if "=" in stripped_line: - key, _ = stripped_line.split("=", 1) - key = key.strip() - - # Check if the key already exists - if key in keys: - duplicates.append((key, keys[key], line_number)) - else: - keys[key] = line_number - - return duplicates - - -# Maximum size for properties files (e.g., 200 KB) -MAX_FILE_SIZE = 200 * 1024 - - -def parse_properties_file(file_path): - """ - Parses a .properties file and returns a structured list of its contents. - :param file_path: Path to the .properties file. - :return: List of dictionaries representing each line in the file. - """ - properties_list = [] - with open(file_path, "r", encoding="utf-8") as file: - for line_number, line in enumerate(file, start=1): - stripped_line = line.strip() - - # Handle empty lines - if not stripped_line: - properties_list.append( - {"line_number": line_number, "type": "empty", "content": ""} - ) - continue - - # Handle comments - if stripped_line.startswith("#"): - properties_list.append( - { - "line_number": line_number, - "type": "comment", - "content": stripped_line, - } - ) - continue - - # Handle key-value pairs - match = re.match(r"^([^=]+)=(.*)$", line) - if match: - key, value = match.groups() - properties_list.append( - { - "line_number": line_number, - "type": "entry", - "key": key.strip(), - "value": value.strip(), - } - ) - - return properties_list - - -def write_json_file(file_path, updated_properties): - """ - Writes updated properties back to the file in their original format. - :param file_path: Path to the .properties file. - :param updated_properties: List of updated properties to write. - """ - updated_lines = {entry["line_number"]: entry for entry in updated_properties} - - # Sort lines by their numbers and retain comments and empty lines - all_lines = sorted(set(updated_lines.keys())) - - original_format = [] - for line in all_lines: - if line in updated_lines: - entry = updated_lines[line] - else: - entry = None - ref_entry = updated_lines[line] - if ref_entry["type"] in ["comment", "empty"]: - original_format.append(ref_entry) - elif entry is None: - # Add missing entries from the reference file - original_format.append(ref_entry) - elif entry["type"] == "entry": - # Replace entries with those from the current JSON - original_format.append(entry) - - # Write the updated content back to the file - with open(file_path, "w", encoding="utf-8", newline="\n") as file: - for entry in original_format: - if entry["type"] == "comment": - file.write(f"{entry['content']}\n") - elif entry["type"] == "empty": - file.write(f"{entry['content']}\n") - elif entry["type"] == "entry": - file.write(f"{entry['key']}={entry['value']}\n") - - -def update_missing_keys(reference_file, file_list, branch=""): - """ - Updates missing keys in the translation files based on the reference file. - :param reference_file: Path to the reference .properties file. - :param file_list: List of translation files to update. - :param branch: Branch where the files are located. - """ - reference_properties = parse_properties_file(reference_file) - for file_path in file_list: - basename_current_file = os.path.basename(os.path.join(branch, file_path)) - if ( - basename_current_file == os.path.basename(reference_file) - or not file_path.endswith(".properties") - or not basename_current_file.startswith("messages_") - ): - continue - - current_properties = parse_properties_file(os.path.join(branch, file_path)) - updated_properties = [] - for ref_entry in reference_properties: - ref_entry_copy = copy.deepcopy(ref_entry) - for current_entry in current_properties: - if current_entry["type"] == "entry": - if ref_entry_copy["type"] != "entry": - continue - if ref_entry_copy["key"].lower() == current_entry["key"].lower(): - ref_entry_copy["value"] = current_entry["value"] - updated_properties.append(ref_entry_copy) - write_json_file(os.path.join(branch, file_path), updated_properties) - - -def check_for_missing_keys(reference_file, file_list, branch): - update_missing_keys(reference_file, file_list, branch) - - -def read_properties(file_path): - if os.path.isfile(file_path) and os.path.exists(file_path): - with open(file_path, "r", encoding="utf-8") as file: - return file.read().splitlines() - return [""] - - -def check_for_differences(reference_file, file_list, branch, actor): - reference_branch = reference_file.split("/")[0] - basename_reference_file = os.path.basename(reference_file) - - report = [] - report.append(f"#### 🔄 Reference Branch: `{reference_branch}`") - reference_lines = read_properties(reference_file) - has_differences = False - - only_reference_file = True - - file_arr = file_list - - if len(file_list) == 1: - file_arr = file_list[0].split() - base_dir = os.path.abspath( - os.path.join(os.getcwd(), "app", "core", "src", "main", "resources") - ) - - for file_path in file_arr: - file_normpath = os.path.normpath(file_path) - absolute_path = os.path.abspath(file_normpath) - # Verify that file is within the expected directory - if not absolute_path.startswith(base_dir): - raise ValueError(f"Unsafe file found: {file_normpath}") - # Verify file size before processing - if os.path.getsize(os.path.join(branch, file_normpath)) > MAX_FILE_SIZE: - raise ValueError( - f"The file {file_normpath} is too large and could pose a security risk." - ) - - basename_current_file = os.path.basename(os.path.join(branch, file_normpath)) - if ( - basename_current_file == basename_reference_file - or ( - # only local windows command - not file_normpath.startswith( - os.path.join( - "", "app", "core", "src", "main", "resources", "messages_" - ) - ) - and not file_normpath.startswith( - os.path.join( - os.getcwd(), - "app", - "core", - "src", - "main", - "resources", - "messages_", - ) - ) - ) - or not file_normpath.endswith(".properties") - or not basename_current_file.startswith("messages_") - ): - continue - only_reference_file = False - report.append(f"#### 📃 **File Check:** `{basename_current_file}`") - current_lines = read_properties(os.path.join(branch, file_path)) - reference_line_count = len(reference_lines) - current_line_count = len(current_lines) - - if reference_line_count != current_line_count: - report.append("") - report.append("1. **Test Status:** ❌ **_Failed_**") - report.append(" - **Issue:**") - has_differences = True - if reference_line_count > current_line_count: - report.append( - f" - **_Mismatched line count_**: {reference_line_count} (reference) vs {current_line_count} (current). Comments, empty lines, or translation strings are missing." - ) - elif reference_line_count < current_line_count: - report.append( - f" - **_Too many lines_**: {reference_line_count} (reference) vs {current_line_count} (current). Please verify if there is an additional line that needs to be removed." - ) - else: - report.append("1. **Test Status:** ✅ **_Passed_**") - - # Check for missing or extra keys - current_keys = [] - reference_keys = [] - for line in current_lines: - if not line.startswith("#") and line != "" and "=" in line: - key, _ = line.split("=", 1) - current_keys.append(key) - for line in reference_lines: - if not line.startswith("#") and line != "" and "=" in line: - key, _ = line.split("=", 1) - reference_keys.append(key) - - current_keys_set = set(current_keys) - reference_keys_set = set(reference_keys) - missing_keys = current_keys_set.difference(reference_keys_set) - extra_keys = reference_keys_set.difference(current_keys_set) - missing_keys_list = list(missing_keys) - extra_keys_list = list(extra_keys) - - if missing_keys_list or extra_keys_list: - has_differences = True - missing_keys_str = "`, `".join(missing_keys_list) - extra_keys_str = "`, `".join(extra_keys_list) - report.append("2. **Test Status:** ❌ **_Failed_**") - report.append(" - **Issue:**") - if missing_keys_list: - spaces_keys_list = [] - for key in missing_keys_list: - if " " in key: - spaces_keys_list.append(key) - if spaces_keys_list: - spaces_keys_str = "`, `".join(spaces_keys_list) - report.append( - f" - **_Keys containing unnecessary spaces_**: `{spaces_keys_str}`!" - ) - report.append( - f" - **_Extra keys in `{basename_current_file}`_**: `{missing_keys_str}` that are not present in **_`{basename_reference_file}`_**." - ) - if extra_keys_list: - report.append( - f" - **_Missing keys in `{basename_reference_file}`_**: `{extra_keys_str}` that are not present in **_`{basename_current_file}`_**." - ) - else: - report.append("2. **Test Status:** ✅ **_Passed_**") - - if find_duplicate_keys(os.path.join(branch, file_normpath)): - has_differences = True - output = "\n".join( - [ - f" - `{key}`: first at line {first}, duplicate at `line {duplicate}`" - for key, first, duplicate in find_duplicate_keys( - os.path.join(branch, file_normpath) - ) - ] - ) - report.append("3. **Test Status:** ❌ **_Failed_**") - report.append(" - **Issue:**") - report.append(" - duplicate entries were found:") - report.append(output) - else: - report.append("3. **Test Status:** ✅ **_Passed_**") - - report.append("") - report.append("---") - report.append("") - if has_differences: - report.append("## ❌ Overall Check Status: **_Failed_**") - report.append("") - report.append( - f"@{actor} please check your translation if it conforms to the standard. Follow the format of [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/app/core/src/main/resources/messages_en_GB.properties)" - ) - else: - report.append("## ✅ Overall Check Status: **_Success_**") - report.append("") - report.append( - f"Thanks @{actor} for your help in keeping the translations up to date." - ) - - if not only_reference_file: - print("\n".join(report)) - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Find missing keys") - parser.add_argument( - "--actor", - required=False, - help="Actor from PR.", - ) - parser.add_argument( - "--reference-file", - required=True, - help="Path to the reference file.", - ) - parser.add_argument( - "--branch", - type=str, - required=True, - help="Branch name.", - ) - parser.add_argument( - "--check-file", - type=str, - required=False, - help="List of changed files, separated by spaces.", - ) - parser.add_argument( - "--files", - nargs="+", - required=False, - help="List of changed files, separated by spaces.", - ) - args = parser.parse_args() - - # Sanitize --actor input to avoid injection attacks - if args.actor: - args.actor = re.sub(r"[^a-zA-Z0-9_\\-]", "", args.actor) - - # Sanitize --branch input to avoid injection attacks - if args.branch: - args.branch = re.sub(r"[^a-zA-Z0-9\\-]", "", args.branch) - - file_list = args.files - if file_list is None: - if args.check_file: - file_list = [args.check_file] - else: - file_list = glob.glob( - os.path.join( - os.getcwd(), - "app", - "core", - "src", - "main", - "resources", - "messages_*.properties", - ) - ) - update_missing_keys(args.reference_file, file_list) - else: - check_for_differences(args.reference_file, file_list, args.branch, args.actor) diff --git a/.github/scripts/check_language_json.py b/.github/scripts/check_language_toml.py similarity index 84% rename from .github/scripts/check_language_json.py rename to .github/scripts/check_language_toml.py index 3921bdaa5..494f90962 100644 --- a/.github/scripts/check_language_json.py +++ b/.github/scripts/check_language_toml.py @@ -1,6 +1,6 @@ """ Author: Ludy87 -Description: This script processes JSON translation files for localization checks. It compares translation files in a branch with +Description: This script processes TOML translation files for localization checks. It compares translation files in a branch with a reference file to ensure consistency. The script performs two main checks: 1. Verifies that the number of translation keys in the translation files matches the reference file. 2. Ensures that all keys in the translation files are present in the reference file and vice versa. @@ -9,10 +9,10 @@ The script also provides functionality to update the translation files to match adjusting the format. Usage: - python check_language_json.py --reference-file --branch [--actor ] [--files ] + python check_language_toml.py --reference-file --branch [--actor ] [--files ] """ # Sample for Windows: -# python .github/scripts/check_language_json.py --reference-file frontend/public/locales/en-GB/translation.json --branch "" --files frontend/public/locales/de-DE/translation.json frontend/public/locales/fr-FR/translation.json +# python .github/scripts/check_language_toml.py --reference-file frontend/public/locales/en-GB/translation.toml --branch "" --files frontend/public/locales/de-DE/translation.toml frontend/public/locales/fr-FR/translation.toml import copy import glob @@ -20,12 +20,14 @@ import os import argparse import re import json +import tomllib # Python 3.11+ (stdlib) +import tomli_w # For writing TOML files def find_duplicate_keys(file_path, keys=None, prefix=""): """ - Identifies duplicate keys in a JSON file (including nested keys). - :param file_path: Path to the JSON file. + Identifies duplicate keys in a TOML file (including nested keys). + :param file_path: Path to the TOML file. :param keys: Dictionary to track keys (used for recursion). :param prefix: Prefix for nested keys. :return: List of tuples (key, first_occurrence_path, duplicate_path). @@ -35,8 +37,9 @@ def find_duplicate_keys(file_path, keys=None, prefix=""): duplicates = [] - with open(file_path, "r", encoding="utf-8") as file: - data = json.load(file) + # Load TOML file + with open(file_path, 'rb') as file: + data = tomllib.load(file) def process_dict(obj, current_prefix=""): for key, value in obj.items(): @@ -54,18 +57,18 @@ def find_duplicate_keys(file_path, keys=None, prefix=""): return duplicates -# Maximum size for JSON files (e.g., 500 KB) +# Maximum size for TOML files (e.g., 500 KB) MAX_FILE_SIZE = 500 * 1024 -def parse_json_file(file_path): +def parse_toml_file(file_path): """ - Parses a JSON translation file and returns a flat dictionary of all keys. - :param file_path: Path to the JSON file. + Parses a TOML translation file and returns a flat dictionary of all keys. + :param file_path: Path to the TOML file. :return: Dictionary with flattened keys. """ - with open(file_path, "r", encoding="utf-8") as file: - data = json.load(file) + with open(file_path, 'rb') as file: + data = tomllib.load(file) def flatten_dict(d, parent_key="", sep="."): items = {} @@ -99,38 +102,37 @@ def unflatten_dict(d, sep="."): return result -def write_json_file(file_path, updated_properties): +def write_toml_file(file_path, updated_properties): """ - Writes updated properties back to the JSON file. - :param file_path: Path to the JSON file. + Writes updated properties back to the TOML file. + :param file_path: Path to the TOML file. :param updated_properties: Dictionary of updated properties to write. """ nested_data = unflatten_dict(updated_properties) - with open(file_path, "w", encoding="utf-8", newline="\n") as file: - json.dump(nested_data, file, ensure_ascii=False, indent=2) - file.write("\n") # Add trailing newline + with open(file_path, "wb") as file: + tomli_w.dump(nested_data, file) def update_missing_keys(reference_file, file_list, branch=""): """ Updates missing keys in the translation files based on the reference file. - :param reference_file: Path to the reference JSON file. + :param reference_file: Path to the reference TOML file. :param file_list: List of translation files to update. :param branch: Branch where the files are located. """ - reference_properties = parse_json_file(reference_file) + reference_properties = parse_toml_file(reference_file) for file_path in file_list: basename_current_file = os.path.basename(os.path.join(branch, file_path)) if ( basename_current_file == os.path.basename(reference_file) - or not file_path.endswith(".json") + or not file_path.endswith(".toml") or not os.path.dirname(file_path).endswith("locales") ): continue - current_properties = parse_json_file(os.path.join(branch, file_path)) + current_properties = parse_toml_file(os.path.join(branch, file_path)) updated_properties = {} for ref_key, ref_value in reference_properties.items(): @@ -141,16 +143,16 @@ def update_missing_keys(reference_file, file_list, branch=""): # Add missing key with reference value updated_properties[ref_key] = ref_value - write_json_file(os.path.join(branch, file_path), updated_properties) + write_toml_file(os.path.join(branch, file_path), updated_properties) def check_for_missing_keys(reference_file, file_list, branch): update_missing_keys(reference_file, file_list, branch) -def read_json_keys(file_path): +def read_toml_keys(file_path): if os.path.isfile(file_path) and os.path.exists(file_path): - return parse_json_file(file_path) + return parse_toml_file(file_path) return {} @@ -160,7 +162,7 @@ def check_for_differences(reference_file, file_list, branch, actor): report = [] report.append(f"#### 🔄 Reference Branch: `{reference_branch}`") - reference_keys = read_json_keys(reference_file) + reference_keys = read_toml_keys(reference_file) has_differences = False only_reference_file = True @@ -197,12 +199,12 @@ def check_for_differences(reference_file, file_list, branch, actor): ): continue - if not file_normpath.endswith(".json") or basename_current_file != "translation.json": + if not file_normpath.endswith(".toml") or basename_current_file != "translation.toml": continue only_reference_file = False report.append(f"#### 📃 **File Check:** `{locale_dir}/{basename_current_file}`") - current_keys = read_json_keys(os.path.join(branch, file_path)) + current_keys = read_toml_keys(os.path.join(branch, file_path)) reference_key_count = len(reference_keys) current_key_count = len(current_keys) @@ -272,7 +274,7 @@ def check_for_differences(reference_file, file_list, branch, actor): report.append("## ❌ Overall Check Status: **_Failed_**") report.append("") report.append( - f"@{actor} please check your translation if it conforms to the standard. Follow the format of [en-GB/translation.json](https://github.com/Stirling-Tools/Stirling-PDF/blob/V2/frontend/public/locales/en-GB/translation.json)" + f"@{actor} please check your translation if it conforms to the standard. Follow the format of [en-GB/translation.toml](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/frontend/public/locales/en-GB/translation.toml)" ) else: report.append("## ✅ Overall Check Status: **_Success_**") @@ -286,7 +288,7 @@ def check_for_differences(reference_file, file_list, branch, actor): if __name__ == "__main__": - parser = argparse.ArgumentParser(description="Find missing keys") + parser = argparse.ArgumentParser(description="Find missing keys in TOML translation files") parser.add_argument( "--actor", required=False, @@ -337,9 +339,9 @@ if __name__ == "__main__": "public", "locales", "*", - "translation.json", + "translation.toml", ) ) update_missing_keys(args.reference_file, file_list) else: - check_for_differences(args.reference_file, file_list, args.branch, args.actor) \ No newline at end of file + check_for_differences(args.reference_file, file_list, args.branch, args.actor) diff --git a/.github/workflows/PR-Auto-Deploy-V2.yml b/.github/workflows/PR-Auto-Deploy-V2.yml index ad6d6f3dc..c96d7d5b2 100644 --- a/.github/workflows/PR-Auto-Deploy-V2.yml +++ b/.github/workflows/PR-Auto-Deploy-V2.yml @@ -52,7 +52,6 @@ jobs: core.setOutput('repository', pr.head.repo.full_name); core.setOutput('ref', pr.head.ref); core.setOutput('is_fork', String(pr.head.repo.fork)); - core.setOutput('base_ref', pr.base.ref); core.setOutput('author', pr.user.login); core.setOutput('state', pr.state); @@ -65,10 +64,6 @@ jobs: IS_FORK: ${{ steps.resolve.outputs.is_fork }} # nur bei workflow_dispatch gesetzt: ALLOW_FORK_INPUT: ${{ inputs.allow_fork }} - # für Auto-PR-Logik: - PR_TITLE: ${{ github.event.pull_request.title }} - PR_BRANCH: ${{ github.event.pull_request.head.ref }} - PR_BASE: ${{ steps.resolve.outputs.base_ref }} PR_AUTHOR: ${{ steps.resolve.outputs.author }} run: | set -e @@ -89,14 +84,8 @@ jobs: else auth_users=("Frooodle" "sf298" "Ludy87" "LaserKaspar" "sbplat" "reecebrowne" "DarioGii" "ConnorYoh" "EthanHealy01" "jbrunton96" "balazs-szucs") is_auth=false; for u in "${auth_users[@]}"; do [ "$u" = "$PR_AUTHOR" ] && is_auth=true && break; done - if [ "$PR_BASE" = "V2" ] && [ "$is_auth" = true ]; then + if [ "$is_auth" = true ]; then should=true - else - title_has_v2=false; echo "$PR_TITLE" | grep -qiE 'v2|version.?2|version.?two' && title_has_v2=true - branch_has_kw=false; echo "$PR_BRANCH" | grep -qiE 'v2|react' && branch_has_kw=true - if [ "$is_auth" = true ] && { [ "$title_has_v2" = true ] || [ "$branch_has_kw" = true ]; }; then - should=true - fi fi fi @@ -174,7 +163,7 @@ jobs: owner, repo, issue_number: prNumber, - body: `🚀 **Auto-deploying V2 version** for PR #${prNumber}...\n\n_This is an automated deployment triggered by V2/version2 keywords in the PR title or V2/React keywords in the branch name._\n\n⚠️ **Note:** If new commits are pushed during deployment, this build will be cancelled and replaced with the latest version.` + body: `🚀 **Auto-deploying V2 version** for PR #${prNumber}...\n\n_This is an automated deployment for approved V2 contributors._\n\n⚠️ **Note:** If new commits are pushed during deployment, this build will be cancelled and replaced with the latest version.` }); return newComment.id; @@ -394,7 +383,7 @@ jobs: `🔗 **Direct Test URL (non-SSL)** [${deploymentUrl}](${deploymentUrl})\n\n` + `🔐 **Secure HTTPS URL**: [${httpsUrl}](${httpsUrl})\n\n` + `_This deployment will be automatically cleaned up when the PR is closed._\n\n` + - `🔄 **Auto-deployed** because PR title or branch name contains V2/version2/React keywords.`; + `🔄 **Auto-deployed** for approved V2 contributors.`; await github.rest.issues.createComment({ owner, diff --git a/.github/workflows/PR-Demo-Comment-with-react.yml b/.github/workflows/PR-Demo-Comment-with-react.yml index bfde13275..c7aa66d4e 100644 --- a/.github/workflows/PR-Demo-Comment-with-react.yml +++ b/.github/workflows/PR-Demo-Comment-with-react.yml @@ -14,6 +14,7 @@ jobs: permissions: issues: write if: | + vars.CI_PROFILE != 'lite' && github.event.issue.pull_request && ( contains(github.event.comment.body, 'prdeploy') || diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9a049bb90..a5df3e9ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -262,7 +262,13 @@ jobs: strategy: fail-fast: false matrix: - docker-rev: ["docker/embedded/Dockerfile", "docker/embedded/Dockerfile.ultra-lite", "docker/embedded/Dockerfile.fat"] + include: + - docker-rev: docker/embedded/Dockerfile + artifact-suffix: Dockerfile + - docker-rev: docker/embedded/Dockerfile.ultra-lite + artifact-suffix: Dockerfile.ultra-lite + - docker-rev: docker/embedded/Dockerfile.fat + artifact-suffix: Dockerfile.fat steps: - name: Harden Runner uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 @@ -272,6 +278,13 @@ jobs: - name: Checkout Repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + - name: Free disk space on runner + run: | + echo "Disk space before cleanup:" && df -h + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android /usr/local/share/boost + docker system prune -af || true + echo "Disk space after cleanup:" && df -h + - name: Set up JDK 17 uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: @@ -313,7 +326,7 @@ jobs: if: always() uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: - name: reports-docker-${{ matrix.docker-rev }} + name: reports-docker-${{ matrix.artifact-suffix }} path: | build/reports/tests/ build/test-results/ diff --git a/.github/workflows/check_properties.yml b/.github/workflows/check_toml.yml similarity index 77% rename from .github/workflows/check_properties.yml rename to .github/workflows/check_toml.yml index 73232eee9..8fc1a75b8 100644 --- a/.github/workflows/check_properties.yml +++ b/.github/workflows/check_toml.yml @@ -1,19 +1,14 @@ -name: Check Properties Files on PR +name: Check TOML Translation Files on PR + +# This workflow validates TOML translation files on: pull_request_target: types: [opened, synchronize, reopened] paths: - - "app/core/src/main/resources/messages_*.properties" + - "frontend/public/locales/*/translation.toml" # cancel in-progress jobs if a new job is triggered -# This is useful to avoid running multiple builds for the same branch if a new commit is pushed -# or a pull request is updated. -# It helps to save resources and time by ensuring that only the latest commit is built and tested -# This is particularly useful for long-running jobs that may take a while to complete. -# The `group` is set to a combination of the workflow name, event name, and branch name. -# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of -# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened. concurrency: group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.event.pull_request.number || github.ref_name || github.ref }} cancel-in-progress: true @@ -73,22 +68,22 @@ jobs: run: | echo "Fetching PR changed files..." echo "Getting list of changed files from PR..." - # Check if PR number exists - if [ -z "${{ steps.get-pr-data.outputs.pr_number }}" ]; then - echo "Error: PR number is empty" - exit 1 - fi - # Get changed files and filter for properties files, handle case where no matches are found - gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^app/core/src/main/resources/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$' > changed_files.txt || echo "No matching properties files found in PR" - # Check if any files were found - if [ ! -s changed_files.txt ]; then - echo "No properties files changed in this PR" - echo "Workflow will exit early as no relevant files to check" - exit 0 - fi - echo "Found $(wc -l < changed_files.txt) matching properties files" + # Check if PR number exists + if [ -z "${{ steps.get-pr-data.outputs.pr_number }}" ]; then + echo "Error: PR number is empty" + exit 1 + fi + # Get changed files and filter for TOML translation files + gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^frontend/public/locales/[a-zA-Z-]+/translation\.toml$' > changed_files.txt || echo "No matching TOML files found in PR" + # Check if any files were found + if [ ! -s changed_files.txt ]; then + echo "No TOML translation files changed in this PR" + echo "Workflow will exit early as no relevant files to check" + exit 0 + fi + echo "Found $(wc -l < changed_files.txt) matching TOML files" - - name: Determine reference file test + - name: Determine reference file id: determine-file uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: @@ -125,11 +120,11 @@ jobs: pull_number: prNumber, }); - // Filter for relevant files based on the PR changes + // Filter for relevant TOML files based on the PR changes const changedFiles = files .filter(file => file.status !== "removed" && - /^app\/core\/src\/main\/resources\/messages_[a-zA-Z_]{2}_[a-zA-Z_]{2,7}\.properties$/.test(file.filename) + /^frontend\/public\/locales\/[a-zA-Z-]+\/translation\.toml$/.test(file.filename) ) .map(file => file.filename); @@ -169,16 +164,16 @@ jobs: // Determine reference file let referenceFilePath; - if (changedFiles.includes("app/core/src/main/resources/messages_en_GB.properties")) { + if (changedFiles.includes("frontend/public/locales/en-GB/translation.toml")) { console.log("Using PR branch reference file."); const { data: fileContent } = await github.rest.repos.getContent({ owner: prRepoOwner, repo: prRepoName, - path: "app/core/src/main/resources/messages_en_GB.properties", + path: "frontend/public/locales/en-GB/translation.toml", ref: branch, }); - referenceFilePath = "pr-branch-messages_en_GB.properties"; + referenceFilePath = "pr-branch-translation-en-GB.toml"; const content = Buffer.from(fileContent.content, "base64").toString("utf-8"); fs.writeFileSync(referenceFilePath, content); } else { @@ -186,11 +181,11 @@ jobs: const { data: fileContent } = await github.rest.repos.getContent({ owner: repoOwner, repo: repoName, - path: "app/core/src/main/resources/messages_en_GB.properties", + path: "frontend/public/locales/en-GB/translation.toml", ref: "main", }); - referenceFilePath = "main-branch-messages_en_GB.properties"; + referenceFilePath = "main-branch-translation-en-GB.toml"; const content = Buffer.from(fileContent.content, "base64").toString("utf-8"); fs.writeFileSync(referenceFilePath, content); } @@ -198,11 +193,20 @@ jobs: console.log(`Reference file path: ${referenceFilePath}`); core.exportVariable("REFERENCE_FILE", referenceFilePath); + - name: Set up Python + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 + with: + python-version: "3.12" + + - name: Install Python dependencies + run: | + pip install tomli-w + - name: Run Python script to check files id: run-check run: | - echo "Running Python script to check files..." - python .github/scripts/check_language_properties.py \ + echo "Running Python script to check TOML files..." + python .github/scripts/check_language_toml.py \ --actor ${{ github.event.pull_request.user.login }} \ --reference-file "${REFERENCE_FILE}" \ --branch "pr-branch" \ @@ -213,7 +217,7 @@ jobs: id: capture-output run: | if [ -f result.txt ] && [ -s result.txt ]; then - echo "Test, capturing output..." + echo "Capturing output..." SCRIPT_OUTPUT=$(cat result.txt) echo "SCRIPT_OUTPUT<> $GITHUB_ENV echo "$SCRIPT_OUTPUT" >> $GITHUB_ENV @@ -227,7 +231,7 @@ jobs: echo "FAIL_JOB=false" >> $GITHUB_ENV fi else - echo "No update found." + echo "No output found." echo "SCRIPT_OUTPUT=" >> $GITHUB_ENV echo "FAIL_JOB=false" >> $GITHUB_ENV fi @@ -249,7 +253,7 @@ jobs: issue_number: issueNumber }); - const comment = comments.data.find(c => c.body.includes("## 🚀 Translation Verification Summary")); + const comment = comments.data.find(c => c.body.includes("## 🌐 TOML Translation Verification Summary")); // Only update or create comments by the action user const expectedActor = "${{ steps.setup-bot.outputs.app-slug }}[bot]"; @@ -260,7 +264,7 @@ jobs: owner: repoOwner, repo: repoName, comment_id: comment.id, - body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n` + body: `## 🌐 TOML Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n` }); console.log("Updated existing comment."); } else if (!comment) { @@ -269,7 +273,7 @@ jobs: owner: repoOwner, repo: repoName, issue_number: issueNumber, - body: `## 🚀 Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n` + body: `## 🌐 TOML Translation Verification Summary\n\n\n${SCRIPT_OUTPUT}\n` }); console.log("Created new comment."); } else { @@ -287,6 +291,6 @@ jobs: run: | echo "Cleaning up temporary files..." rm -rf pr-branch - rm -f pr-branch-messages_en_GB.properties main-branch-messages_en_GB.properties changed_files.txt result.txt + rm -f pr-branch-translation-en-GB.toml main-branch-translation-en-GB.toml changed_files.txt result.txt echo "Cleanup complete." continue-on-error: true # Ensure cleanup runs even if previous steps fail diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml index 636a34bc0..07c03b083 100644 --- a/.github/workflows/multiOSReleases.yml +++ b/.github/workflows/multiOSReleases.yml @@ -31,6 +31,7 @@ permissions: jobs: determine-matrix: + if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} diff --git a/.github/workflows/push-docker-v2.yml b/.github/workflows/push-docker-v2.yml index 5f2b70f50..061cf40ed 100644 --- a/.github/workflows/push-docker-v2.yml +++ b/.github/workflows/push-docker-v2.yml @@ -24,6 +24,7 @@ permissions: jobs: push: + if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-24.04-8core permissions: packages: write diff --git a/.github/workflows/push-docker.yml b/.github/workflows/push-docker.yml index 3f2a0c8d0..ecf3fdc95 100644 --- a/.github/workflows/push-docker.yml +++ b/.github/workflows/push-docker.yml @@ -24,6 +24,7 @@ permissions: jobs: push: + if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest permissions: packages: write diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index ca037b7c0..d83accd49 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -17,6 +17,7 @@ permissions: read-all jobs: analysis: + if: ${{ vars.CI_PROFILE != 'lite' }} name: Scorecard analysis runs-on: ubuntu-latest permissions: diff --git a/.github/workflows/sonarqube.yml b/.github/workflows/sonarqube.yml index 146eb4b39..dd419b310 100644 --- a/.github/workflows/sonarqube.yml +++ b/.github/workflows/sonarqube.yml @@ -27,6 +27,7 @@ permissions: jobs: sonarqube: + if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest steps: - name: Harden Runner diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index c3c0b110a..c53bb4a4b 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -10,6 +10,7 @@ permissions: jobs: stale: + if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest permissions: issues: write diff --git a/.github/workflows/swagger.yml b/.github/workflows/swagger.yml index 16f0a3088..6e9cdb435 100644 --- a/.github/workflows/swagger.yml +++ b/.github/workflows/swagger.yml @@ -23,6 +23,7 @@ permissions: jobs: push: + if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest steps: - name: Harden Runner diff --git a/.github/workflows/sync_files.yml b/.github/workflows/sync_files.yml deleted file mode 100644 index 1233ac701..000000000 --- a/.github/workflows/sync_files.yml +++ /dev/null @@ -1,122 +0,0 @@ -name: Sync Files - -on: - workflow_dispatch: - push: - branches: - - main - paths: - - "build.gradle" - - "README.md" - - "app/core/src/main/resources/messages_*.properties" - - "app/core/src/main/resources/static/3rdPartyLicenses.json" - - "scripts/ignore_translation.toml" - -# cancel in-progress jobs if a new job is triggered -# This is useful to avoid running multiple builds for the same branch if a new commit is pushed -# or a pull request is updated. -# It helps to save resources and time by ensuring that only the latest commit is built and tested -# This is particularly useful for long-running jobs that may take a while to complete. -# The `group` is set to a combination of the workflow name, event name, and branch name. -# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of -# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened. -concurrency: - group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }} - cancel-in-progress: true - -permissions: - contents: read - -jobs: - sync-files: - runs-on: ubuntu-latest - env: - # Prevents sdist builds → no tar extraction - PIP_ONLY_BINARY: ":all:" - PIP_DISABLE_PIP_VERSION_CHECK: "1" - steps: - - name: Harden Runner - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a # v2.13.1 - with: - egress-policy: audit - - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Setup GitHub App Bot - id: setup-bot - uses: ./.github/actions/setup-bot - with: - app-id: ${{ secrets.GH_APP_ID }} - private-key: ${{ secrets.GH_APP_PRIVATE_KEY }} - - - name: Set up Python - uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c # v6.0.0 - with: - python-version: "3.12" - cache: "pip" # caching pip dependencies - - - name: Sync translation property files - run: | - python .github/scripts/check_language_properties.py --reference-file "app/core/src/main/resources/messages_en_GB.properties" --branch main - - - name: Commit translation files - run: | - git add app/core/src/main/resources/messages_*.properties - git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "No changes detected" - - - name: Install dependencies - # Wheels-only + Hash-Pinning - run: | - pip install --require-hashes --only-binary=:all: -r ./.github/scripts/requirements_sync_readme.txt - - - name: Sync README.md - run: | - python scripts/counter_translation.py - - - name: Run git add - run: | - git add README.md scripts/ignore_translation.toml - git diff --staged --quiet || git commit -m ":memo: Sync README.md & scripts/ignore_translation.toml" || echo "No changes detected" - - - name: Create Pull Request - if: always() - uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 - with: - token: ${{ steps.setup-bot.outputs.token }} - commit-message: Update files - committer: ${{ steps.setup-bot.outputs.committer }} - author: ${{ steps.setup-bot.outputs.committer }} - signoff: true - branch: sync_readme - title: ":globe_with_meridians: Sync Translations + Update README Progress Table" - body: | - ### Description of Changes - - This Pull Request was automatically generated to synchronize updates to translation files and documentation. Below are the details of the changes made: - - #### **1. Synchronization of Translation Files** - - Updated translation files (`messages_*.properties`) to reflect changes in the reference file `messages_en_GB.properties`. - - Ensured consistency and synchronization across all supported language files. - - Highlighted any missing or incomplete translations. - - #### **2. Update README.md** - - Generated the translation progress table in `README.md`. - - Added a summary of the current translation status for all supported languages. - - Included up-to-date statistics on translation coverage. - - #### **Why these changes are necessary** - - Keeps translation files aligned with the latest reference updates. - - Ensures the documentation reflects the current translation progress. - - --- - - Auto-generated by [create-pull-request][1]. - - [1]: https://github.com/peter-evans/create-pull-request - draft: false - delete-branch: true - labels: github-actions - sign-commits: true - add-paths: | - README.md - app/core/src/main/resources/messages_*.properties diff --git a/.github/workflows/sync_files_v2.yml b/.github/workflows/sync_files_v2.yml index 84645c59e..935252be2 100644 --- a/.github/workflows/sync_files_v2.yml +++ b/.github/workflows/sync_files_v2.yml @@ -1,15 +1,15 @@ -name: Sync Files V2 +name: Sync Files (TOML) on: workflow_dispatch: push: branches: - - V2 + - main - syncLangTest paths: - "build.gradle" - "README.md" - - "frontend/public/locales/*/translation.json" + - "frontend/public/locales/*/translation.toml" - "app/core/src/main/resources/static/3rdPartyLicenses.json" - "scripts/ignore_translation.toml" @@ -52,21 +52,25 @@ jobs: python-version: "3.12" cache: "pip" # caching pip dependencies - - name: Sync translation JSON files + - name: Install Python dependencies run: | - python .github/scripts/check_language_json.py --reference-file "frontend/public/locales/en-GB/translation.json" --branch V2 + pip install tomli-w + + - name: Sync translation TOML files + run: | + python .github/scripts/check_language_toml.py --reference-file "frontend/public/locales/en-GB/translation.toml" --branch main - name: Commit translation files run: | - git add frontend/public/locales/*/translation.json - git diff --staged --quiet || git commit -m ":memo: Sync translation files" || echo "No changes detected" + git add frontend/public/locales/*/translation.toml + git diff --staged --quiet || git commit -m ":memo: Sync translation files (TOML)" || echo "No changes detected" - - name: Install dependencies + - name: Install README dependencies run: pip install --require-hashes -r ./.github/scripts/requirements_sync_readme.txt - name: Sync README.md run: | - python scripts/counter_translation_v2.py + python scripts/counter_translation_v3.py - name: Run git add run: | @@ -82,21 +86,22 @@ jobs: committer: ${{ steps.setup-bot.outputs.committer }} author: ${{ steps.setup-bot.outputs.committer }} signoff: true - branch: sync_readme_v2 - base: V2 - title: ":globe_with_meridians: [V2] Sync Translations + Update README Progress Table" + branch: sync_readme_v3 + base: main + title: ":globe_with_meridians: Sync Translations + Update README Progress Table" body: | ### Description of Changes - This Pull Request was automatically generated to synchronize updates to translation files and documentation for the **V2 branch**. Below are the details of the changes made: + This Pull Request was automatically generated to synchronize updates to translation files and documentation. Below are the details of the changes made: #### **1. Synchronization of Translation Files** - - Updated translation files (`frontend/public/locales/*/translation.json`) to reflect changes in the reference file `en-GB/translation.json`. + - Updated translation files (`frontend/public/locales/*/translation.toml`) to reflect changes in the reference file `en-GB/translation.toml`. - Ensured consistency and synchronization across all supported language files. - Highlighted any missing or incomplete translations. + - **Format**: TOML #### **2. Update README.md** - - Generated the translation progress table in `README.md`. + - Generated the translation progress table in `README.md` using `counter_translation_v3.py`. - Added a summary of the current translation status for all supported languages. - Included up-to-date statistics on translation coverage. @@ -115,4 +120,5 @@ jobs: sign-commits: true add-paths: | README.md - frontend/public/locales/*/translation.json \ No newline at end of file + frontend/public/locales/*/translation.toml + scripts/ignore_translation.toml \ No newline at end of file diff --git a/.github/workflows/tauri-build.yml b/.github/workflows/tauri-build.yml index 4e153d519..d28683950 100644 --- a/.github/workflows/tauri-build.yml +++ b/.github/workflows/tauri-build.yml @@ -28,6 +28,7 @@ permissions: jobs: determine-matrix: + if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest outputs: matrix: ${{ steps.set-matrix.outputs.matrix }} @@ -636,6 +637,8 @@ jobs: if [ "${{ needs.build.result }}" = "success" ]; then echo "✅ All Tauri builds completed successfully!" echo "Artifacts are ready for distribution." + elif [ "${{ needs.build.result }}" = "skipped" ]; then + echo "⏭️ Tauri builds skipped (CI lite mode enabled)" else echo "❌ Some Tauri builds failed." echo "Please check the logs and fix any issues." diff --git a/.github/workflows/testdriver.yml b/.github/workflows/testdriver.yml index 828c84d62..12d5bc48d 100644 --- a/.github/workflows/testdriver.yml +++ b/.github/workflows/testdriver.yml @@ -21,6 +21,7 @@ permissions: jobs: deploy: + if: ${{ vars.CI_PROFILE != 'lite' }} runs-on: ubuntu-latest steps: - name: Harden Runner diff --git a/ADDING_TOOLS.md b/ADDING_TOOLS.md index ef1501bfc..d24641b00 100644 --- a/ADDING_TOOLS.md +++ b/ADDING_TOOLS.md @@ -202,10 +202,10 @@ const [ToolName] = (props: BaseToolProps) => { ## 5. Add Translations Update translation files. **Important: Only update `en-GB` files** - other languages are handled separately. -**File to update:** `frontend/public/locales/en-GB/translation.json` +**File to update:** `frontend/public/locales/en-GB/translation.toml` **Required Translation Keys**: -```json +```toml { "home": { "[toolName]": { @@ -251,7 +251,7 @@ Update translation files. **Important: Only update `en-GB` files** - other langu ``` **Translation Notes:** -- **Only update `en-GB/translation.json`** - other locale files are managed separately +- **Only update `en-GB/translation.toml`** - other locale files are managed separately - Use descriptive keys that match your component's `t()` calls - Include tooltip translations if you created tooltip hooks - Add `options.*` keys if your tool has settings with descriptions diff --git a/app/common/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java b/app/common/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java index 35f68939c..ff48b5b2e 100644 --- a/app/common/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java +++ b/app/common/src/main/java/stirling/software/SPDF/config/EndpointConfiguration.java @@ -491,6 +491,9 @@ public class EndpointConfiguration { addEndpointToGroup("Ghostscript", "repair"); addEndpointToGroup("Ghostscript", "compress-pdf"); + /* ImageMagick */ + addEndpointToGroup("ImageMagick", "compress-pdf"); + /* tesseract */ addEndpointToGroup("tesseract", "ocr-pdf"); @@ -574,6 +577,7 @@ public class EndpointConfiguration { || "Javascript".equals(group) || "Weasyprint".equals(group) || "Pdftohtml".equals(group) + || "ImageMagick".equals(group) || "rar".equals(group); } diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index 6a6ee8453..72cfef1a0 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -68,6 +68,7 @@ public class ApplicationProperties { private AutoPipeline autoPipeline = new AutoPipeline(); private ProcessExecutor processExecutor = new ProcessExecutor(); + private PdfEditor pdfEditor = new PdfEditor(); @Bean public PropertySource dynamicYamlPropertySource(ConfigurableEnvironment environment) @@ -100,6 +101,46 @@ public class ApplicationProperties { private String outputFolder; } + @Data + public static class PdfEditor { + private Cache cache = new Cache(); + private FontNormalization fontNormalization = new FontNormalization(); + private CffConverter cffConverter = new CffConverter(); + private Type3 type3 = new Type3(); + private String fallbackFont = "classpath:/static/fonts/NotoSans-Regular.ttf"; + + @Data + public static class Cache { + private long maxBytes = -1; + private int maxPercent = 20; + } + + @Data + public static class FontNormalization { + private boolean enabled = false; + } + + @Data + public static class CffConverter { + private boolean enabled = true; + private String method = "python"; + private String pythonCommand = "/opt/venv/bin/python3"; + private String pythonScript = "/scripts/convert_cff_to_ttf.py"; + private String fontforgeCommand = "fontforge"; + } + + @Data + public static class Type3 { + private Library library = new Library(); + + @Data + public static class Library { + private boolean enabled = true; + private String index = "classpath:/type3/library/index.json"; + } + } + } + @Data public static class Legal { private String termsAndConditions; @@ -112,7 +153,6 @@ public class ApplicationProperties { @Data public static class Security { private Boolean enableLogin; - private Boolean csrfDisabled; private InitialLogin initialLogin = new InitialLogin(); private OAUTH2 oauth2 = new OAUTH2(); private SAML2 saml2 = new SAML2(); @@ -358,6 +398,7 @@ public class ApplicationProperties { private Boolean enableAnalytics; private Boolean enablePosthog; private Boolean enableScarf; + private Boolean enableDesktopInstallSlide; private Datasource datasource; private Boolean disableSanitize; private int maxDPI; @@ -368,10 +409,12 @@ public class ApplicationProperties { private TempFileManagement tempFileManagement = new TempFileManagement(); private DatabaseBackup databaseBackup = new DatabaseBackup(); private List corsAllowedOrigins = new ArrayList<>(); - private String - frontendUrl; // Base URL for frontend (used for invite links, etc.). If not set, + private String backendUrl; // Backend base URL for SAML/OAuth/API callbacks (e.g. + // 'http://localhost:8080', 'https://api.example.com'). Required for + // SSO. + private String frontendUrl; // Frontend URL for invite email links (e.g. - // falls back to backend URL. + // 'https://app.example.com'). If not set, falls back to backendUrl. public boolean isAnalyticsEnabled() { return this.getEnableAnalytics() != null && this.getEnableAnalytics(); @@ -536,6 +579,7 @@ public class ApplicationProperties { @ToString.Exclude private String key; private String UUID; private String appVersion; + private Boolean isNewServer; } // TODO: Remove post migration @@ -575,6 +619,16 @@ public class ApplicationProperties { private String username; @ToString.Exclude private String password; private String from; + // STARTTLS upgrades a plain SMTP connection to TLS after connecting (RFC 3207) + private Boolean startTlsEnable = true; + private Boolean startTlsRequired; + // SSL/TLS wrapper for implicit TLS (typically port 465) + private Boolean sslEnable; + // Hostnames or patterns (e.g., "smtp.example.com" or "*") to trust for TLS certificates; + // defaults to "*" (trust all) when not set + private String sslTrust; + // Enables hostname verification for TLS connections + private Boolean sslCheckServerIdentity; } @Data @@ -643,6 +697,7 @@ public class ApplicationProperties { private int weasyPrintSessionLimit; private int installAppSessionLimit; private int calibreSessionLimit; + private int imageMagickSessionLimit; private int qpdfSessionLimit; private int tesseractSessionLimit; private int ghostscriptSessionLimit; @@ -680,6 +735,10 @@ public class ApplicationProperties { return calibreSessionLimit > 0 ? calibreSessionLimit : 1; } + public int getImageMagickSessionLimit() { + return imageMagickSessionLimit > 0 ? imageMagickSessionLimit : 4; + } + public int getGhostscriptSessionLimit() { return ghostscriptSessionLimit > 0 ? ghostscriptSessionLimit : 8; } @@ -709,6 +768,8 @@ public class ApplicationProperties { @JsonProperty("calibretimeoutMinutes") private long calibreTimeoutMinutes; + private long imageMagickTimeoutMinutes; + private long tesseractTimeoutMinutes; private long qpdfTimeoutMinutes; private long ghostscriptTimeoutMinutes; @@ -746,6 +807,10 @@ public class ApplicationProperties { return calibreTimeoutMinutes > 0 ? calibreTimeoutMinutes : 30; } + public long getImageMagickTimeoutMinutes() { + return imageMagickTimeoutMinutes > 0 ? imageMagickTimeoutMinutes : 30; + } + public long getGhostscriptTimeoutMinutes() { return ghostscriptTimeoutMinutes > 0 ? ghostscriptTimeoutMinutes : 30; } diff --git a/app/common/src/main/java/stirling/software/common/service/LineArtConversionService.java b/app/common/src/main/java/stirling/software/common/service/LineArtConversionService.java new file mode 100644 index 000000000..ab4f55d2e --- /dev/null +++ b/app/common/src/main/java/stirling/software/common/service/LineArtConversionService.java @@ -0,0 +1,12 @@ +package stirling.software.common.service; + +import java.io.IOException; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; + +public interface LineArtConversionService { + PDImageXObject convertImageToLineArt( + PDDocument doc, PDImageXObject originalImage, double threshold, int edgeLevel) + throws IOException; +} diff --git a/app/common/src/main/java/stirling/software/common/service/PostHogService.java b/app/common/src/main/java/stirling/software/common/service/PostHogService.java index 310fc43ab..786c04a43 100644 --- a/app/common/src/main/java/stirling/software/common/service/PostHogService.java +++ b/app/common/src/main/java/stirling/software/common/service/PostHogService.java @@ -254,10 +254,7 @@ public class PostHogService { properties, "security_enableLogin", applicationProperties.getSecurity().getEnableLogin()); - addIfNotEmpty( - properties, - "security_csrfDisabled", - applicationProperties.getSecurity().getCsrfDisabled()); + addIfNotEmpty(properties, "security_csrfDisabled", true); addIfNotEmpty( properties, "security_loginAttemptCount", diff --git a/app/common/src/main/java/stirling/software/common/util/ProcessExecutor.java b/app/common/src/main/java/stirling/software/common/util/ProcessExecutor.java index 3b94fbfbc..269441813 100644 --- a/app/common/src/main/java/stirling/software/common/util/ProcessExecutor.java +++ b/app/common/src/main/java/stirling/software/common/util/ProcessExecutor.java @@ -86,6 +86,11 @@ public class ProcessExecutor { .getProcessExecutor() .getSessionLimit() .getCalibreSessionLimit(); + case IMAGEMAGICK -> + applicationProperties + .getProcessExecutor() + .getSessionLimit() + .getImageMagickSessionLimit(); case GHOSTSCRIPT -> applicationProperties .getProcessExecutor() @@ -141,6 +146,11 @@ public class ProcessExecutor { .getProcessExecutor() .getTimeoutMinutes() .getCalibreTimeoutMinutes(); + case IMAGEMAGICK -> + applicationProperties + .getProcessExecutor() + .getTimeoutMinutes() + .getImageMagickTimeoutMinutes(); case GHOSTSCRIPT -> applicationProperties .getProcessExecutor() @@ -301,6 +311,7 @@ public class ProcessExecutor { WEASYPRINT, INSTALL_APP, CALIBRE, + IMAGEMAGICK, TESSERACT, QPDF, GHOSTSCRIPT, diff --git a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java index a0adec7de..acad6f4a9 100644 --- a/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java +++ b/app/common/src/main/java/stirling/software/common/util/RequestUriUtils.java @@ -26,6 +26,7 @@ public class RequestUriUtils { || normalizedUri.startsWith("/public/") || normalizedUri.startsWith("/pdfjs/") || normalizedUri.startsWith("/pdfjs-legacy/") + || normalizedUri.startsWith("/pdfium/") || normalizedUri.startsWith("/assets/") || normalizedUri.startsWith("/locales/") || normalizedUri.startsWith("/Login/") @@ -61,7 +62,8 @@ public class RequestUriUtils { || normalizedUri.endsWith(".css") || normalizedUri.endsWith(".mjs") || normalizedUri.endsWith(".html") - || normalizedUri.endsWith(".toml"); + || normalizedUri.endsWith(".toml") + || normalizedUri.endsWith(".wasm"); } public static boolean isFrontendRoute(String contextPath, String requestURI) { @@ -125,11 +127,13 @@ public class RequestUriUtils { || requestURI.endsWith("popularity.txt") || requestURI.endsWith(".js") || requestURI.endsWith(".toml") + || requestURI.endsWith(".wasm") || requestURI.contains("swagger") || requestURI.startsWith("/api/v1/info") || requestURI.startsWith("/site.webmanifest") || requestURI.startsWith("/fonts") - || requestURI.startsWith("/pdfjs")); + || requestURI.startsWith("/pdfjs") + || requestURI.startsWith("/pdfium")); } /** @@ -162,10 +166,9 @@ public class RequestUriUtils { // enableLogin) || trimmedUri.startsWith( "/api/v1/ui-data/footer-info") // Public footer configuration - || trimmedUri.startsWith("/v1/api-docs") || trimmedUri.startsWith("/api/v1/invite/validate") || trimmedUri.startsWith("/api/v1/invite/accept") - || trimmedUri.contains("/v1/api-docs"); + || trimmedUri.startsWith("/v1/api-docs"); } private static String stripContextPath(String contextPath, String requestURI) { diff --git a/app/common/src/test/java/stirling/software/common/util/RequestUriUtilsTest.java b/app/common/src/test/java/stirling/software/common/util/RequestUriUtilsTest.java index b7c121ab3..409615e68 100644 --- a/app/common/src/test/java/stirling/software/common/util/RequestUriUtilsTest.java +++ b/app/common/src/test/java/stirling/software/common/util/RequestUriUtilsTest.java @@ -24,6 +24,9 @@ public class RequestUriUtilsTest { assertTrue( RequestUriUtils.isStaticResource("/pdfjs/pdf.worker.js"), "PDF.js files should be static"); + assertTrue( + RequestUriUtils.isStaticResource("/pdfium/pdfium.wasm"), + "PDFium wasm should be static"); assertTrue( RequestUriUtils.isStaticResource("/api/v1/info/status"), "API status should be static"); @@ -110,7 +113,8 @@ public class RequestUriUtilsTest { "/downloads/document.png", "/assets/brand.ico", "/any/path/with/image.svg", - "/deep/nested/folder/icon.png" + "/deep/nested/folder/icon.png", + "/pdfium/pdfium.wasm" }) void testIsStaticResourceWithFileExtensions(String path) { assertTrue( @@ -148,6 +152,9 @@ public class RequestUriUtilsTest { assertFalse( RequestUriUtils.isTrackableResource("/script.js"), "JS files should not be trackable"); + assertFalse( + RequestUriUtils.isTrackableResource("/pdfium/pdfium.wasm"), + "PDFium wasm should not be trackable"); assertFalse( RequestUriUtils.isTrackableResource("/swagger/index.html"), "Swagger files should not be trackable"); @@ -224,7 +231,8 @@ public class RequestUriUtilsTest { "/api/v1/info/health", "/site.webmanifest", "/fonts/roboto.woff", - "/pdfjs/viewer.js" + "/pdfjs/viewer.js", + "/pdfium/pdfium.wasm" }) void testNonTrackableResources(String path) { assertFalse( diff --git a/app/core/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java b/app/core/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java index 59c8825fc..8606cc2a9 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/ExternalAppDepConfig.java @@ -46,6 +46,7 @@ public class ExternalAppDepConfig { put("qpdf", List.of("qpdf")); put("tesseract", List.of("tesseract")); put("rar", List.of("rar")); // Required for real CBR output + put("magick", List.of("ImageMagick")); } }; } @@ -128,6 +129,7 @@ public class ExternalAppDepConfig { checkDependencyAndDisableGroup("pdftohtml"); checkDependencyAndDisableGroup(unoconvPath); checkDependencyAndDisableGroup("rar"); + checkDependencyAndDisableGroup("magick"); // Special handling for Python/OpenCV dependencies boolean pythonAvailable = isCommandAvailable("python3") || isCommandAvailable("python"); if (!pythonAvailable) { diff --git a/app/core/src/main/java/stirling/software/SPDF/config/InitialSetup.java b/app/core/src/main/java/stirling/software/SPDF/config/InitialSetup.java index 0a63a6f48..2cded405f 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/InitialSetup.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/InitialSetup.java @@ -34,7 +34,6 @@ public class InitialSetup { public void init() throws IOException { initUUIDKey(); initSecretKey(); - initEnableCSRFSecurity(); initLegalUrls(); initSetAppVersion(); GeneralUtils.extractPipeline(); @@ -60,18 +59,6 @@ public class InitialSetup { } } - public void initEnableCSRFSecurity() throws IOException { - if (GeneralUtils.isVersionHigher( - "0.46.0", applicationProperties.getAutomaticallyGenerated().getAppVersion())) { - Boolean csrf = applicationProperties.getSecurity().getCsrfDisabled(); - if (!csrf) { - GeneralUtils.saveKeyToSettings("security.csrfDisabled", false); - GeneralUtils.saveKeyToSettings("system.enableAnalytics", true); - applicationProperties.getSecurity().setCsrfDisabled(false); - } - } - } - public void initLegalUrls() throws IOException { // Initialize Terms and Conditions String termsUrl = applicationProperties.getLegal().getTermsAndConditions(); @@ -95,7 +82,7 @@ public class InitialSetup { isNewServer = existingVersion == null || existingVersion.isEmpty() - || existingVersion.equals("0.0.0"); + || "0.0.0".equals(existingVersion); String appVersion = "0.0.0"; Resource resource = new ClassPathResource("version.properties"); @@ -107,6 +94,7 @@ public class InitialSetup { } GeneralUtils.saveKeyToSettings("AutomaticallyGenerated.appVersion", appVersion); applicationProperties.getAutomaticallyGenerated().setAppVersion(appVersion); + applicationProperties.getAutomaticallyGenerated().setIsNewServer(isNewServer); } public static boolean isNewServer() { diff --git a/app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java b/app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java index a25287c4d..c85e78b73 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/OpenApiConfig.java @@ -62,10 +62,15 @@ public class OpenApiConfig { // Add server configuration from environment variable String swaggerServerUrl = System.getenv("SWAGGER_SERVER_URL"); + Server server; if (swaggerServerUrl != null && !swaggerServerUrl.trim().isEmpty()) { - Server server = new Server().url(swaggerServerUrl).description("API Server"); - openAPI.addServersItem(server); + server = new Server().url(swaggerServerUrl).description("API Server"); + } else { + // Use relative path so Swagger uses the current browser origin to avoid CORS issues + // when accessing via different ports + server = new Server().url("/").description("Current Server"); } + openAPI.addServersItem(server); // Add ErrorResponse schema to components Schema errorResponseSchema = diff --git a/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java index 0703708f5..8eac8fa80 100644 --- a/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java +++ b/app/core/src/main/java/stirling/software/SPDF/config/WebMvcConfig.java @@ -1,10 +1,14 @@ package stirling.software.SPDF.config; +import java.util.concurrent.TimeUnit; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Configuration; +import org.springframework.http.CacheControl; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import lombok.RequiredArgsConstructor; @@ -25,6 +29,20 @@ public class WebMvcConfig implements WebMvcConfigurer { registry.addInterceptor(endpointInterceptor); } + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + // Cache hashed assets (JS/CSS with content hashes) for 1 year + // These files have names like index-ChAS4tCC.js that change when content changes + registry.addResourceHandler("/assets/**") + .addResourceLocations("classpath:/static/assets/") + .setCacheControl(CacheControl.maxAge(365, TimeUnit.DAYS).cachePublic()); + + // Don't cache index.html - it needs to be fresh to reference latest hashed assets + registry.addResourceHandler("/index.html") + .addResourceLocations("classpath:/static/") + .setCacheControl(CacheControl.noCache().mustRevalidate()); + } + @Override public void addCorsMappings(CorsRegistry registry) { // Check if running in Tauri mode diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java index 9657d8f15..1d9f63818 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/SettingsController.java @@ -124,7 +124,6 @@ public class SettingsController { ApplicationProperties.Security security = applicationProperties.getSecurity(); settings.put("enableLogin", security.getEnableLogin()); - settings.put("csrfDisabled", security.getCsrfDisabled()); settings.put("loginMethod", security.getLoginMethod()); settings.put("loginAttemptCount", security.getLoginAttemptCount()); settings.put("loginResetTimeMinutes", security.getLoginResetTimeMinutes()); @@ -159,12 +158,6 @@ public class SettingsController { .getSecurity() .setEnableLogin((Boolean) settings.get("enableLogin")); } - if (settings.containsKey("csrfDisabled")) { - GeneralUtils.saveKeyToSettings("security.csrfDisabled", settings.get("csrfDisabled")); - applicationProperties - .getSecurity() - .setCsrfDisabled((Boolean) settings.get("csrfDisabled")); - } if (settings.containsKey("loginMethod")) { GeneralUtils.saveKeyToSettings("security.loginMethod", settings.get("loginMethod")); applicationProperties diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java similarity index 99% rename from app/proprietary/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java rename to app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java index ef29c72e5..3b9f98c8c 100644 --- a/app/proprietary/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonController.java @@ -31,12 +31,10 @@ import stirling.software.common.model.api.PDFFile; import stirling.software.common.service.JobOwnershipService; import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.WebResponseUtils; -import stirling.software.proprietary.security.config.PremiumEndpoint; @Slf4j @ConvertApi @RequiredArgsConstructor -@PremiumEndpoint public class ConvertPdfJsonController { private final PdfJsonConversionService pdfJsonConversionService; diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonExceptionHandler.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonExceptionHandler.java new file mode 100644 index 000000000..c82fe19e1 --- /dev/null +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/converters/ConvertPdfJsonExceptionHandler.java @@ -0,0 +1,60 @@ +package stirling.software.SPDF.controller.api.converters; + +import java.nio.charset.StandardCharsets; + +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import stirling.software.SPDF.exception.CacheUnavailableException; + +@ControllerAdvice(assignableTypes = ConvertPdfJsonController.class) +@Slf4j +@RequiredArgsConstructor +public class ConvertPdfJsonExceptionHandler { + + private final ObjectMapper objectMapper; + + @ExceptionHandler(CacheUnavailableException.class) + @ResponseBody + public ResponseEntity handleCacheUnavailable(CacheUnavailableException ex) { + try { + byte[] body = + objectMapper.writeValueAsBytes( + java.util.Map.of( + "error", "cache_unavailable", + "action", "reupload", + "message", ex.getMessage())); + return ResponseEntity.status(HttpStatus.GONE) + .contentType(MediaType.APPLICATION_JSON) + .body(body); + } catch (Exception e) { + log.warn("Failed to serialize cache_unavailable response", e); + var fallbackBody = + java.util.Map.of( + "error", "cache_unavailable", + "action", "reupload", + "message", String.valueOf(ex.getMessage())); + try { + return ResponseEntity.status(HttpStatus.GONE) + .contentType(MediaType.APPLICATION_JSON) + .body(objectMapper.writeValueAsBytes(fallbackBody)); + } catch (Exception ignored) { + // Truly last-ditch fallback + return ResponseEntity.status(HttpStatus.GONE) + .contentType(MediaType.APPLICATION_JSON) + .body( + "{\"error\":\"cache_unavailable\",\"action\":\"reupload\",\"message\":\"Cache unavailable\"}" + .getBytes(StandardCharsets.UTF_8)); + } + } + } +} diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java index 865a95c5c..9b0483da9 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/CompressController.java @@ -28,10 +28,13 @@ import org.apache.pdfbox.pdmodel.PDResources; import org.apache.pdfbox.pdmodel.graphics.PDXObject; import org.apache.pdfbox.pdmodel.graphics.form.PDFormXObject; import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.server.ResponseStatusException; import io.swagger.v3.oas.annotations.Operation; @@ -44,6 +47,7 @@ import stirling.software.SPDF.model.api.misc.OptimizePdfRequest; import stirling.software.common.annotations.AutoJobPostMapping; import stirling.software.common.annotations.api.MiscApi; import stirling.software.common.service.CustomPDFDocumentFactory; +import stirling.software.common.service.LineArtConversionService; import stirling.software.common.util.ExceptionUtils; import stirling.software.common.util.GeneralUtils; import stirling.software.common.util.ProcessExecutor; @@ -58,6 +62,9 @@ public class CompressController { private final CustomPDFDocumentFactory pdfDocumentFactory; private final EndpointConfiguration endpointConfiguration; + @Autowired(required = false) + private LineArtConversionService lineArtConversionService; + private boolean isQpdfEnabled() { return endpointConfiguration.isGroupEnabled("qpdf"); } @@ -66,6 +73,10 @@ public class CompressController { return endpointConfiguration.isGroupEnabled("Ghostscript"); } + private boolean isImageMagickEnabled() { + return endpointConfiguration.isGroupEnabled("ImageMagick"); + } + @Data @AllArgsConstructor @NoArgsConstructor @@ -660,6 +671,9 @@ public class CompressController { Integer optimizeLevel = request.getOptimizeLevel(); String expectedOutputSizeString = request.getExpectedOutputSize(); Boolean convertToGrayscale = request.getGrayscale(); + Boolean convertToLineArt = request.getLineArt(); + Double lineArtThreshold = request.getLineArtThreshold(); + Integer lineArtEdgeLevel = request.getLineArtEdgeLevel(); if (expectedOutputSizeString == null && optimizeLevel == null) { throw new Exception("Both expected output size and optimize level are not specified"); } @@ -689,6 +703,26 @@ public class CompressController { optimizeLevel = determineOptimizeLevel(sizeReductionRatio); } + if (Boolean.TRUE.equals(convertToLineArt)) { + if (lineArtConversionService == null) { + throw new ResponseStatusException( + HttpStatus.FORBIDDEN, + "Line art conversion is unavailable - ImageMagick service not found"); + } + if (!isImageMagickEnabled()) { + throw new IOException( + "ImageMagick is not enabled but line art conversion was requested"); + } + double thresholdValue = + lineArtThreshold == null + ? 55d + : Math.min(100d, Math.max(0d, lineArtThreshold)); + int edgeLevel = + lineArtEdgeLevel == null ? 1 : Math.min(3, Math.max(1, lineArtEdgeLevel)); + currentFile = + applyLineArtConversion(currentFile, tempFiles, thresholdValue, edgeLevel); + } + boolean sizeMet = false; boolean imageCompressionApplied = false; boolean externalCompressionApplied = false; @@ -810,6 +844,75 @@ public class CompressController { } } + private Path applyLineArtConversion( + Path currentFile, List tempFiles, double threshold, int edgeLevel) + throws IOException { + + Path lineArtFile = Files.createTempFile("lineart_output_", ".pdf"); + tempFiles.add(lineArtFile); + + try (PDDocument doc = pdfDocumentFactory.load(currentFile.toFile())) { + Map> uniqueImages = findImages(doc); + CompressionStats stats = new CompressionStats(); + stats.uniqueImagesCount = uniqueImages.size(); + calculateImageStats(uniqueImages, stats); + + Map convertedImages = + createLineArtImages(doc, uniqueImages, stats, threshold, edgeLevel); + + replaceImages(doc, uniqueImages, convertedImages, stats); + + log.info( + "Applied line art conversion to {} unique images ({} total references)", + stats.uniqueImagesCount, + stats.totalImages); + + doc.save(lineArtFile.toString()); + return lineArtFile; + } + } + + private Map createLineArtImages( + PDDocument doc, + Map> uniqueImages, + CompressionStats stats, + double threshold, + int edgeLevel) + throws IOException { + + Map convertedImages = new HashMap<>(); + + for (Entry> entry : uniqueImages.entrySet()) { + String imageHash = entry.getKey(); + List references = entry.getValue(); + if (references.isEmpty()) continue; + + PDImageXObject originalImage = getOriginalImage(doc, references.get(0)); + + int originalSize = (int) originalImage.getCOSObject().getLength(); + stats.totalOriginalBytes += originalSize; + + PDImageXObject converted = + lineArtConversionService.convertImageToLineArt( + doc, originalImage, threshold, edgeLevel); + convertedImages.put(imageHash, converted); + stats.compressedImages++; + + int convertedSize = (int) converted.getCOSObject().getLength(); + stats.totalCompressedBytes += convertedSize * references.size(); + + double reductionPercentage = 100.0 - ((convertedSize * 100.0) / originalSize); + log.info( + "Image hash {}: Line art conversion {} → {} (reduced by {}%)", + imageHash, + GeneralUtils.formatBytes(originalSize), + GeneralUtils.formatBytes(convertedSize), + String.format("%.1f", reductionPercentage)); + } + + return convertedImages; + } + // Run Ghostscript compression private void applyGhostscriptCompression( OptimizePdfRequest request, int optimizeLevel, Path currentFile, List tempFiles) diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java index 4f31b83ef..95486ff9b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/misc/ConfigController.java @@ -74,6 +74,7 @@ public class ConfigController { configData.put("appNameNavbar", applicationProperties.getUi().getAppNameNavbar()); configData.put("languages", applicationProperties.getUi().getLanguages()); configData.put("logoStyle", applicationProperties.getUi().getLogoStyle()); + configData.put("defaultLocale", applicationProperties.getSystem().getDefaultLocale()); // Security settings // enableLogin requires both the config flag AND proprietary features to be loaded @@ -123,6 +124,9 @@ public class ConfigController { "enableAnalytics", applicationProperties.getSystem().getEnableAnalytics()); configData.put("enablePosthog", applicationProperties.getSystem().getEnablePosthog()); configData.put("enableScarf", applicationProperties.getSystem().getEnableScarf()); + configData.put( + "enableDesktopInstallSlide", + applicationProperties.getSystem().getEnableDesktopInstallSlide()); // Premium/Enterprise settings configData.put("premiumEnabled", applicationProperties.getPremium().isEnabled()); @@ -226,4 +230,10 @@ public class ConfigController { } return ResponseEntity.ok(result); } + + @GetMapping("/group-enabled") + public ResponseEntity isGroupEnabled(@RequestParam(name = "group") String group) { + boolean enabled = endpointConfiguration.isGroupEnabled(group); + return ResponseEntity.ok(enabled); + } } diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java index 3b27492a6..893c9c54c 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/security/CertSignController.java @@ -191,6 +191,12 @@ public class CertSignController { switch (certType) { case "PEM": + privateKeyFile = + validateFilePresent( + privateKeyFile, "PEM private key", "private key file is required"); + certFile = + validateFilePresent( + certFile, "PEM certificate", "certificate file is required"); ks = KeyStore.getInstance("JKS"); ks.load(null); PrivateKey privateKey = getPrivateKeyFromPEM(privateKeyFile.getBytes(), password); @@ -200,10 +206,16 @@ public class CertSignController { break; case "PKCS12": case "PFX": + p12File = + validateFilePresent( + p12File, "PKCS12 keystore", "PKCS12/PFX keystore file is required"); ks = KeyStore.getInstance("PKCS12"); ks.load(p12File.getInputStream(), password.toCharArray()); break; case "JKS": + jksfile = + validateFilePresent( + jksfile, "JKS keystore", "JKS keystore file is required"); ks = KeyStore.getInstance("JKS"); ks.load(jksfile.getInputStream(), password.toCharArray()); break; @@ -251,6 +263,17 @@ public class CertSignController { GeneralUtils.generateFilename(pdf.getOriginalFilename(), "_signed.pdf")); } + private MultipartFile validateFilePresent( + MultipartFile file, String argumentName, String errorDescription) { + if (file == null || file.isEmpty()) { + throw ExceptionUtils.createIllegalArgumentException( + "error.invalidArgument", + "Invalid argument: {0}", + argumentName + " - " + errorDescription); + } + return file; + } + private PrivateKey getPrivateKeyFromPEM(byte[] pemBytes, String password) throws IOException, OperatorCreationException, PKCSException { try (PEMParser pemParser = diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java b/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java index adde1020a..ab8f3b75b 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/web/ReactRoutingController.java @@ -11,6 +11,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; +import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; @Controller @@ -19,10 +20,25 @@ public class ReactRoutingController { @Value("${server.servlet.context-path:/}") private String contextPath; - @GetMapping( - value = {"/", "/index.html"}, - produces = MediaType.TEXT_HTML_VALUE) - public ResponseEntity serveIndexHtml(HttpServletRequest request) throws IOException { + private String cachedIndexHtml; + private boolean indexHtmlExists = false; + + @PostConstruct + public void init() { + // Only cache if index.html exists (production builds) + ClassPathResource resource = new ClassPathResource("static/index.html"); + if (resource.exists()) { + try { + this.cachedIndexHtml = processIndexHtml(); + this.indexHtmlExists = true; + } catch (IOException e) { + // Failed to cache, will process on each request + this.indexHtmlExists = false; + } + } + } + + private String processIndexHtml() throws IOException { ClassPathResource resource = new ClassPathResource("static/index.html"); try (InputStream inputStream = resource.getInputStream()) { @@ -42,18 +58,29 @@ public class ReactRoutingController { ""; html = html.replace("", contextPathScript + ""); - return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(html); + return html; } } @GetMapping( - "/{path:^(?!api|static|robots\\.txt|favicon\\.ico|manifest.*\\.json|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*$}") + value = {"/", "/index.html"}, + produces = MediaType.TEXT_HTML_VALUE) + public ResponseEntity serveIndexHtml(HttpServletRequest request) throws IOException { + if (indexHtmlExists && cachedIndexHtml != null) { + return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(cachedIndexHtml); + } + // Fallback: process on each request (dev mode or cache failed) + return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(processIndexHtml()); + } + + @GetMapping( + "/{path:^(?!api|static|robots\\.txt|favicon\\.ico|manifest.*\\.json|pipeline|pdfjs|pdfjs-legacy|pdfium|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*$}") public ResponseEntity forwardRootPaths(HttpServletRequest request) throws IOException { return serveIndexHtml(request); } @GetMapping( - "/{path:^(?!api|static|pipeline|pdfjs|pdfjs-legacy|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*}/{subpath:^(?!.*\\.).*$}") + "/{path:^(?!api|static|pipeline|pdfjs|pdfjs-legacy|pdfium|fonts|images|files|css|js|assets|locales|modern-logo|classic-logo|Login|og_images|samples)[^\\.]*}/{subpath:^(?!.*\\.).*$}") public ResponseEntity forwardNestedPaths(HttpServletRequest request) throws IOException { return serveIndexHtml(request); diff --git a/app/core/src/main/java/stirling/software/SPDF/exception/CacheUnavailableException.java b/app/core/src/main/java/stirling/software/SPDF/exception/CacheUnavailableException.java new file mode 100644 index 000000000..fd5d77677 --- /dev/null +++ b/app/core/src/main/java/stirling/software/SPDF/exception/CacheUnavailableException.java @@ -0,0 +1,8 @@ +package stirling.software.SPDF.exception; + +public class CacheUnavailableException extends RuntimeException { + + public CacheUnavailableException(String message) { + super(message); + } +} diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/api/PdfJsonConversionProgress.java b/app/core/src/main/java/stirling/software/SPDF/model/api/PdfJsonConversionProgress.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/api/PdfJsonConversionProgress.java rename to app/core/src/main/java/stirling/software/SPDF/model/api/PdfJsonConversionProgress.java diff --git a/app/core/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java b/app/core/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java index bf96dd217..d6e5c7021 100644 --- a/app/core/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java +++ b/app/core/src/main/java/stirling/software/SPDF/model/api/misc/OptimizePdfRequest.java @@ -45,4 +45,26 @@ public class OptimizePdfRequest extends PDFFile { requiredMode = Schema.RequiredMode.REQUIRED, defaultValue = "false") private Boolean grayscale = false; + + @Schema( + description = + "Whether to convert images to high-contrast line art using ImageMagick. Default is false.", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, + defaultValue = "false") + private Boolean lineArt = false; + + @Schema( + description = "Threshold to use for line art conversion (0-100).", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, + defaultValue = "55") + private Double lineArtThreshold = 55d; + + @Schema( + description = + "Edge detection strength to use for line art conversion (1-3). This maps to" + + " ImageMagick's -edge radius.", + requiredMode = Schema.RequiredMode.NOT_REQUIRED, + defaultValue = "1", + allowableValues = {"1", "2", "3"}) + private Integer lineArtEdgeLevel = 1; } diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonAnnotation.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonAnnotation.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonAnnotation.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonAnnotation.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonCosValue.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonCosValue.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonCosValue.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonCosValue.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonDocument.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonDocument.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonDocument.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonDocument.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonDocumentMetadata.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonDocumentMetadata.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonDocumentMetadata.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonDocumentMetadata.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFont.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFont.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFont.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFont.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontCidSystemInfo.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontCidSystemInfo.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontCidSystemInfo.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontCidSystemInfo.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontConversionCandidate.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontConversionCandidate.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontConversionCandidate.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontConversionCandidate.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontConversionStatus.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontConversionStatus.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontConversionStatus.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontConversionStatus.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontType3Glyph.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontType3Glyph.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontType3Glyph.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFontType3Glyph.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFormField.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFormField.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonFormField.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonFormField.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonImageElement.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonImageElement.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonImageElement.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonImageElement.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonMetadata.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonMetadata.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonMetadata.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonMetadata.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonPage.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonPage.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonPage.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonPage.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonPageDimension.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonPageDimension.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonPageDimension.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonPageDimension.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonStream.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonStream.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonStream.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonStream.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonTextColor.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonTextColor.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonTextColor.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonTextColor.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonTextElement.java b/app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonTextElement.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/model/json/PdfJsonTextElement.java rename to app/core/src/main/java/stirling/software/SPDF/model/json/PdfJsonTextElement.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java similarity index 95% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java rename to app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java index 623b99260..604e9ba38 100644 --- a/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonConversionService.java @@ -86,7 +86,6 @@ import org.apache.pdfbox.text.PDFTextStripper; import org.apache.pdfbox.text.TextPosition; import org.apache.pdfbox.util.DateConverter; import org.apache.pdfbox.util.Matrix; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -144,15 +143,23 @@ public class PdfJsonConversionService { private final PdfJsonFontService fontService; private final Type3FontConversionService type3FontConversionService; private final Type3GlyphExtractor type3GlyphExtractor; + private final stirling.software.common.model.ApplicationProperties applicationProperties; private final Map type3NormalizedFontCache = new ConcurrentHashMap<>(); private final Map> type3GlyphCoverageCache = new ConcurrentHashMap<>(); - @Value("${stirling.pdf.json.font-normalization.enabled:true}") private boolean fontNormalizationEnabled; + private long cacheMaxBytes; + private int cacheMaxPercent; /** Cache for storing PDDocuments for lazy page loading. Key is jobId. */ private final Map documentCache = new ConcurrentHashMap<>(); + private final java.util.LinkedHashMap lruCache = + new java.util.LinkedHashMap<>(16, 0.75f, true); + private final Object cacheLock = new Object(); + private volatile long currentCacheBytes = 0L; + private volatile long cacheBudgetBytes = -1L; + private volatile boolean ghostscriptAvailable; private static final float FLOAT_EPSILON = 0.0001f; @@ -161,7 +168,23 @@ public class PdfJsonConversionService { @PostConstruct private void initializeToolAvailability() { + loadConfigurationFromProperties(); initializeGhostscriptAvailability(); + initializeCacheBudget(); + } + + private void loadConfigurationFromProperties() { + stirling.software.common.model.ApplicationProperties.PdfEditor cfg = + applicationProperties.getPdfEditor(); + if (cfg != null) { + fontNormalizationEnabled = cfg.getFontNormalization().isEnabled(); + cacheMaxBytes = cfg.getCache().getMaxBytes(); + cacheMaxPercent = cfg.getCache().getMaxPercent(); + } else { + fontNormalizationEnabled = false; + cacheMaxBytes = -1; + cacheMaxPercent = 20; + } } private void initializeGhostscriptAvailability() { @@ -202,6 +225,25 @@ public class PdfJsonConversionService { } } + private void initializeCacheBudget() { + long effective = -1L; + if (cacheMaxBytes > 0) { + effective = cacheMaxBytes; + } else if (cacheMaxPercent > 0) { + long maxMem = Runtime.getRuntime().maxMemory(); + effective = Math.max(0L, (maxMem * cacheMaxPercent) / 100); + } + cacheBudgetBytes = effective; + if (cacheBudgetBytes > 0) { + log.info( + "PDF JSON cache budget configured: {} bytes (source: {})", + cacheBudgetBytes, + cacheMaxBytes > 0 ? "max-bytes" : "max-percent"); + } else { + log.info("PDF JSON cache budget: unlimited"); + } + } + public byte[] convertPdfToJson(MultipartFile file) throws IOException { return convertPdfToJson(file, null, false); } @@ -236,7 +278,10 @@ public class PdfJsonConversionService { log.debug("Generated synthetic jobId for synchronous conversion: {}", jobId); } else { jobId = contextJobId; - log.debug("Starting PDF to JSON conversion, jobId from context: {}", jobId); + log.info( + "Starting PDF to JSON conversion, jobId from context: {} (lightweight={})", + jobId, + lightweight); } Consumer progress = @@ -318,9 +363,9 @@ public class PdfJsonConversionService { try (PDDocument document = pdfDocumentFactory.load(workingPath, true)) { int totalPages = document.getNumberOfPages(); - // Only use lazy images for real async jobs where client can access the cache - // Synchronous calls with synthetic jobId should do full extraction - boolean useLazyImages = totalPages > 5 && isRealJobId; + // Always enable lazy mode for real async jobs so cache is available regardless of + // page count. Synchronous calls with synthetic jobId still do full extraction. + boolean useLazyImages = isRealJobId; Map fontCache = new IdentityHashMap<>(); Map imageCache = new IdentityHashMap<>(); log.debug( @@ -403,6 +448,11 @@ public class PdfJsonConversionService { // Only cache for real async jobIds, not synthetic synchronous ones if (useLazyImages && isRealJobId) { + log.info( + "Creating cache for jobId: {} (useLazyImages={}, isRealJobId={})", + jobId, + useLazyImages, + isRealJobId); PdfJsonDocumentMetadata docMetadata = new PdfJsonDocumentMetadata(); docMetadata.setMetadata(pdfJson.getMetadata()); docMetadata.setXmpMetadata(pdfJson.getXmpMetadata()); @@ -435,16 +485,23 @@ public class PdfJsonConversionService { cachedPdfBytes = Files.readAllBytes(workingPath); } CachedPdfDocument cached = - new CachedPdfDocument( - cachedPdfBytes, docMetadata, fonts, pageFontResources); - documentCache.put(jobId, cached); - log.debug( - "Cached PDF bytes ({} bytes, {} pages, {} fonts) for lazy images, jobId: {}", - cachedPdfBytes.length, + buildCachedDocument( + jobId, cachedPdfBytes, docMetadata, fonts, pageFontResources); + putCachedDocument(jobId, cached); + log.info( + "Successfully cached PDF ({} bytes, {} pages, {} fonts) for jobId: {} (diskBacked={})", + cached.getPdfSize(), totalPages, fonts.size(), - jobId); + jobId, + cached.isDiskBacked()); scheduleDocumentCleanup(jobId); + } else { + log.warn( + "Skipping cache creation: useLazyImages={}, isRealJobId={}, jobId={}", + useLazyImages, + isRealJobId, + jobId); } if (lightweight) { @@ -2973,6 +3030,139 @@ public class PdfJsonConversionService { } } + // Cache helpers + private CachedPdfDocument buildCachedDocument( + String jobId, + byte[] pdfBytes, + PdfJsonDocumentMetadata metadata, + Map fonts, + Map> pageFontResources) + throws IOException { + if (pdfBytes == null) { + throw new IllegalArgumentException("pdfBytes must not be null"); + } + long budget = cacheBudgetBytes; + // If single document is larger than budget, spill straight to disk + if (budget > 0 && pdfBytes.length > budget) { + TempFile tempFile = new TempFile(tempFileManager, ".pdfjsoncache"); + Files.write(tempFile.getPath(), pdfBytes); + log.debug( + "Cached PDF spilled to disk ({} bytes exceeds budget {}) for jobId {}", + pdfBytes.length, + budget, + jobId); + return new CachedPdfDocument( + null, tempFile, pdfBytes.length, metadata, fonts, pageFontResources); + } + return new CachedPdfDocument( + pdfBytes, null, pdfBytes.length, metadata, fonts, pageFontResources); + } + + private void putCachedDocument(String jobId, CachedPdfDocument cached) { + synchronized (cacheLock) { + CachedPdfDocument existing = documentCache.put(jobId, cached); + if (existing != null) { + lruCache.remove(jobId); + currentCacheBytes = Math.max(0L, currentCacheBytes - existing.getInMemorySize()); + existing.close(); + } + lruCache.put(jobId, cached); + currentCacheBytes += cached.getInMemorySize(); + enforceCacheBudget(); + } + } + + private CachedPdfDocument getCachedDocument(String jobId) { + synchronized (cacheLock) { + CachedPdfDocument cached = documentCache.get(jobId); + if (cached != null) { + lruCache.remove(jobId); + lruCache.put(jobId, cached); + } + return cached; + } + } + + private void enforceCacheBudget() { + if (cacheBudgetBytes <= 0) { + return; + } + // Must be called under cacheLock + java.util.Iterator> it = + lruCache.entrySet().iterator(); + while (currentCacheBytes > cacheBudgetBytes && it.hasNext()) { + java.util.Map.Entry entry = it.next(); + it.remove(); + CachedPdfDocument removed = entry.getValue(); + documentCache.remove(entry.getKey(), removed); + currentCacheBytes = Math.max(0L, currentCacheBytes - removed.getInMemorySize()); + removed.close(); + log.warn( + "Evicted cached PDF for jobId {} to enforce cache budget (budget={} bytes, current={} bytes)", + entry.getKey(), + cacheBudgetBytes, + currentCacheBytes); + } + if (currentCacheBytes > cacheBudgetBytes && !lruCache.isEmpty()) { + // Spill the most recently used large entry to disk + String key = + lruCache.entrySet().stream() + .reduce((first, second) -> second) + .map(java.util.Map.Entry::getKey) + .orElse(null); + if (key != null) { + CachedPdfDocument doc = lruCache.get(key); + if (doc != null && doc.getInMemorySize() > 0) { + try { + CachedPdfDocument diskDoc = + buildCachedDocument( + key, + doc.getPdfBytes(), + doc.getMetadata(), + doc.getFonts(), + doc.getPageFontResources()); + lruCache.put(key, diskDoc); + documentCache.put(key, diskDoc); + currentCacheBytes = + Math.max(0L, currentCacheBytes - doc.getInMemorySize()) + + diskDoc.getInMemorySize(); + doc.close(); + log.debug("Spilled cached PDF for jobId {} to disk to satisfy budget", key); + } catch (IOException ex) { + log.warn( + "Failed to spill cached PDF for jobId {} to disk: {}", + key, + ex.getMessage()); + } + } + } + } + } + + private void removeCachedDocument(String jobId) { + log.warn( + "removeCachedDocument called for jobId: {} [CALLER: {}]", + jobId, + Thread.currentThread().getStackTrace()[2].toString()); + CachedPdfDocument removed = null; + synchronized (cacheLock) { + removed = documentCache.remove(jobId); + if (removed != null) { + lruCache.remove(jobId); + currentCacheBytes = Math.max(0L, currentCacheBytes - removed.getInMemorySize()); + log.warn( + "Removed cached document for jobId: {} (size={} bytes)", + jobId, + removed.getInMemorySize()); + } else { + log.warn("Attempted to remove jobId: {} but it was not in cache", jobId); + } + } + if (removed != null) { + removed.close(); + } + } + private void applyTextState(PDPageContentStream contentStream, PdfJsonTextElement element) throws IOException { if (element.getCharacterSpacing() != null) { @@ -5311,6 +5501,8 @@ public class PdfJsonConversionService { */ private static class CachedPdfDocument { private final byte[] pdfBytes; + private final TempFile pdfTempFile; + private final long pdfSize; private final PdfJsonDocumentMetadata metadata; private final Map fonts; // Font map with UIDs for consistency private final Map> pageFontResources; // Page font resources @@ -5318,10 +5510,14 @@ public class PdfJsonConversionService { public CachedPdfDocument( byte[] pdfBytes, + TempFile pdfTempFile, + long pdfSize, PdfJsonDocumentMetadata metadata, Map fonts, Map> pageFontResources) { this.pdfBytes = pdfBytes; + this.pdfTempFile = pdfTempFile; + this.pdfSize = pdfSize; this.metadata = metadata; // Create defensive copies to prevent mutation of shared maps this.fonts = @@ -5336,8 +5532,14 @@ public class PdfJsonConversionService { } // Getters return defensive copies to prevent external mutation - public byte[] getPdfBytes() { - return pdfBytes; + public byte[] getPdfBytes() throws IOException { + if (pdfBytes != null) { + return pdfBytes; + } + if (pdfTempFile != null) { + return Files.readAllBytes(pdfTempFile.getPath()); + } + throw new IOException("Cached PDF backing missing"); } public PdfJsonDocumentMetadata getMetadata() { @@ -5352,6 +5554,18 @@ public class PdfJsonConversionService { return new java.util.concurrent.ConcurrentHashMap<>(pageFontResources); } + public long getPdfSize() { + return pdfSize; + } + + public long getInMemorySize() { + return pdfBytes != null ? pdfBytes.length : 0L; + } + + public boolean isDiskBacked() { + return pdfBytes == null && pdfTempFile != null; + } + public long getTimestamp() { return timestamp; } @@ -5363,7 +5577,19 @@ public class PdfJsonConversionService { public CachedPdfDocument withUpdatedFonts( byte[] nextBytes, Map nextFonts) { Map fontsToUse = nextFonts != null ? nextFonts : this.fonts; - return new CachedPdfDocument(nextBytes, metadata, fontsToUse, pageFontResources); + return new CachedPdfDocument( + nextBytes, + null, + nextBytes != null ? nextBytes.length : 0, + metadata, + fontsToUse, + pageFontResources); + } + + public void close() { + if (pdfTempFile != null) { + pdfTempFile.close(); + } } } @@ -5444,14 +5670,15 @@ public class PdfJsonConversionService { // Cache PDF bytes, metadata, and fonts for lazy page loading if (jobId != null) { CachedPdfDocument cached = - new CachedPdfDocument(pdfBytes, docMetadata, fonts, pageFontResources); - documentCache.put(jobId, cached); + buildCachedDocument(jobId, pdfBytes, docMetadata, fonts, pageFontResources); + putCachedDocument(jobId, cached); log.debug( - "Cached PDF bytes ({} bytes, {} pages, {} fonts) for lazy loading, jobId: {}", - pdfBytes.length, + "Cached PDF bytes ({} bytes, {} pages, {} fonts) for lazy loading, jobId: {} (diskBacked={})", + cached.getPdfSize(), totalPages, fonts.size(), - jobId); + jobId, + cached.isDiskBacked()); // Schedule cleanup after 30 minutes scheduleDocumentCleanup(jobId); @@ -5466,9 +5693,10 @@ public class PdfJsonConversionService { /** Extracts a single page from cached PDF bytes. Re-loads the PDF for each request. */ public byte[] extractSinglePage(String jobId, int pageNumber) throws IOException { - CachedPdfDocument cached = documentCache.get(jobId); + CachedPdfDocument cached = getCachedDocument(jobId); if (cached == null) { - throw new IllegalArgumentException("No cached document found for jobId: " + jobId); + throw new stirling.software.SPDF.exception.CacheUnavailableException( + "No cached document found for jobId: " + jobId); } int pageIndex = pageNumber - 1; @@ -5480,8 +5708,8 @@ public class PdfJsonConversionService { } log.debug( - "Loading PDF from bytes ({} bytes) to extract page {} (jobId: {})", - cached.getPdfBytes().length, + "Loading PDF from {} to extract page {} (jobId: {})", + cached.isDiskBacked() ? "disk cache" : "memory cache", pageNumber, jobId); @@ -5627,10 +5855,21 @@ public class PdfJsonConversionService { if (jobId == null || jobId.isBlank()) { throw new IllegalArgumentException("jobId is required for incremental export"); } - CachedPdfDocument cached = documentCache.get(jobId); + log.info("Looking up cache for jobId: {}", jobId); + CachedPdfDocument cached = getCachedDocument(jobId); if (cached == null) { - throw new IllegalArgumentException("No cached document available for jobId: " + jobId); + log.error( + "Cache not found for jobId: {}. Available cache keys: {}", + jobId, + documentCache.keySet()); + throw new stirling.software.SPDF.exception.CacheUnavailableException( + "No cached document available for jobId: " + jobId); } + log.info( + "Found cached document for jobId: {} (size={}, diskBacked={})", + jobId, + cached.getPdfSize(), + cached.isDiskBacked()); if (updates == null || updates.getPages() == null || updates.getPages().isEmpty()) { log.debug( "Incremental export requested with no page updates; returning cached PDF for jobId {}", @@ -5709,7 +5948,14 @@ public class PdfJsonConversionService { document.save(baos); byte[] updatedBytes = baos.toByteArray(); - documentCache.put(jobId, cached.withUpdatedFonts(updatedBytes, mergedFonts)); + CachedPdfDocument updated = + buildCachedDocument( + jobId, + updatedBytes, + cached.getMetadata(), + mergedFonts, + cached.getPageFontResources()); + putCachedDocument(jobId, updated); // Clear Type3 cache entries for this incremental update clearType3CacheEntriesForJob(updateJobId); @@ -5724,11 +5970,13 @@ public class PdfJsonConversionService { /** Clears a cached document. */ public void clearCachedDocument(String jobId) { - CachedPdfDocument cached = documentCache.remove(jobId); + CachedPdfDocument cached = getCachedDocument(jobId); + removeCachedDocument(jobId); if (cached != null) { log.debug( - "Removed cached PDF bytes ({} bytes) for jobId: {}", - cached.getPdfBytes().length, + "Removed cached PDF ({} bytes, diskBacked={}) for jobId: {}", + cached.getPdfSize(), + cached.isDiskBacked(), jobId); } diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonCosMapper.java b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonCosMapper.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonCosMapper.java rename to app/core/src/main/java/stirling/software/SPDF/service/PdfJsonCosMapper.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java similarity index 86% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java rename to app/core/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java index 4cf0fc8a1..e4baee055 100644 --- a/app/proprietary/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/PdfJsonFallbackFontService.java @@ -33,8 +33,12 @@ public class PdfJsonFallbackFontService { public static final String FALLBACK_FONT_CJK_ID = "fallback-noto-cjk"; public static final String FALLBACK_FONT_JP_ID = "fallback-noto-jp"; public static final String FALLBACK_FONT_KR_ID = "fallback-noto-korean"; + public static final String FALLBACK_FONT_TC_ID = "fallback-noto-tc"; public static final String FALLBACK_FONT_AR_ID = "fallback-noto-arabic"; public static final String FALLBACK_FONT_TH_ID = "fallback-noto-thai"; + public static final String FALLBACK_FONT_DEVANAGARI_ID = "fallback-noto-devanagari"; + public static final String FALLBACK_FONT_MALAYALAM_ID = "fallback-noto-malayalam"; + public static final String FALLBACK_FONT_TIBETAN_ID = "fallback-noto-tibetan"; // Font name aliases map PDF font names to available fallback fonts // This provides better visual consistency when editing PDFs @@ -59,6 +63,22 @@ public class PdfJsonFallbackFontService { Map.entry("dejavuserif", "fallback-dejavu-serif"), Map.entry("dejavumono", "fallback-dejavu-mono"), Map.entry("dejavusansmono", "fallback-dejavu-mono"), + // Traditional Chinese fonts (Taiwan, Hong Kong, Macau) + Map.entry("mingliu", "fallback-noto-tc"), + Map.entry("pmingliu", "fallback-noto-tc"), + Map.entry("microsoftjhenghei", "fallback-noto-tc"), + Map.entry("jhenghei", "fallback-noto-tc"), + Map.entry("kaiti", "fallback-noto-tc"), + Map.entry("kaiu", "fallback-noto-tc"), + Map.entry("dfkaib5", "fallback-noto-tc"), + Map.entry("dfkai", "fallback-noto-tc"), + // Simplified Chinese fonts (Mainland China) - more common + Map.entry("simsun", "fallback-noto-cjk"), + Map.entry("simhei", "fallback-noto-cjk"), + Map.entry("microsoftyahei", "fallback-noto-cjk"), + Map.entry("yahei", "fallback-noto-cjk"), + Map.entry("songti", "fallback-noto-cjk"), + Map.entry("heiti", "fallback-noto-cjk"), // Noto Sans - Google's universal font (use as last resort generic fallback) Map.entry("noto", "fallback-noto-sans"), Map.entry("notosans", "fallback-noto-sans")); @@ -83,6 +103,12 @@ public class PdfJsonFallbackFontService { "classpath:/static/fonts/NotoSansKR-Regular.ttf", "NotoSansKR-Regular", "ttf")), + Map.entry( + FALLBACK_FONT_TC_ID, + new FallbackFontSpec( + "classpath:/static/fonts/NotoSansTC-Regular.ttf", + "NotoSansTC-Regular", + "ttf")), Map.entry( FALLBACK_FONT_AR_ID, new FallbackFontSpec( @@ -95,6 +121,24 @@ public class PdfJsonFallbackFontService { "classpath:/static/fonts/NotoSansThai-Regular.ttf", "NotoSansThai-Regular", "ttf")), + Map.entry( + FALLBACK_FONT_DEVANAGARI_ID, + new FallbackFontSpec( + "classpath:/static/fonts/NotoSansDevanagari-Regular.ttf", + "NotoSansDevanagari-Regular", + "ttf")), + Map.entry( + FALLBACK_FONT_MALAYALAM_ID, + new FallbackFontSpec( + "classpath:/static/fonts/NotoSansMalayalam-Regular.ttf", + "NotoSansMalayalam-Regular", + "ttf")), + Map.entry( + FALLBACK_FONT_TIBETAN_ID, + new FallbackFontSpec( + "classpath:/static/fonts/NotoSerifTibetan-Regular.ttf", + "NotoSerifTibetan-Regular", + "ttf")), // Liberation Sans family Map.entry( "fallback-liberation-sans", @@ -268,12 +312,29 @@ public class PdfJsonFallbackFontService { "ttf"))); private final ResourceLoader resourceLoader; + private final stirling.software.common.model.ApplicationProperties applicationProperties; @Value("${stirling.pdf.fallback-font:" + DEFAULT_FALLBACK_FONT_LOCATION + "}") + private String legacyFallbackFontLocation; + private String fallbackFontLocation; private final Map fallbackFontCache = new ConcurrentHashMap<>(); + @jakarta.annotation.PostConstruct + private void loadConfig() { + String configured = null; + if (applicationProperties.getPdfEditor() != null) { + configured = applicationProperties.getPdfEditor().getFallbackFont(); + } + if (configured != null && !configured.isBlank()) { + fallbackFontLocation = configured; + } else { + fallbackFontLocation = legacyFallbackFontLocation; + } + log.info("Using fallback font location: {}", fallbackFontLocation); + } + public PdfJsonFont buildFallbackFontModel() throws IOException { return buildFallbackFontModel(FALLBACK_FONT_ID); } @@ -484,6 +545,20 @@ public class PdfJsonFallbackFontService { */ public String resolveFallbackFontId(int codePoint) { Character.UnicodeBlock block = Character.UnicodeBlock.of(codePoint); + + // Bopomofo is primarily used in Taiwan for Traditional Chinese phonetic annotation + if (block == Character.UnicodeBlock.BOPOMOFO + || block == Character.UnicodeBlock.BOPOMOFO_EXTENDED) { + return FALLBACK_FONT_TC_ID; + } + + // Compatibility ideographs are primarily used by Traditional Chinese encodings (e.g., Big5, + // HKSCS) so prefer the Traditional Chinese fallback here. + if (block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS + || block == Character.UnicodeBlock.CJK_COMPATIBILITY_IDEOGRAPHS_SUPPLEMENT) { + return FALLBACK_FONT_TC_ID; + } + if (block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS || block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_A || block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_B @@ -492,19 +567,23 @@ public class PdfJsonFallbackFontService { || block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_E || block == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS_EXTENSION_F || block == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION - || block == Character.UnicodeBlock.BOPOMOFO - || block == Character.UnicodeBlock.BOPOMOFO_EXTENDED || block == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS) { return FALLBACK_FONT_CJK_ID; } Character.UnicodeScript script = Character.UnicodeScript.of(codePoint); return switch (script) { + // HAN script is used by both Simplified and Traditional Chinese + // Default to Simplified (mainland China, 1.4B speakers) as it's more common + // Traditional Chinese PDFs are detected via font name aliases (MingLiU, PMingLiU, etc.) case HAN -> FALLBACK_FONT_CJK_ID; case HIRAGANA, KATAKANA -> FALLBACK_FONT_JP_ID; case HANGUL -> FALLBACK_FONT_KR_ID; case ARABIC -> FALLBACK_FONT_AR_ID; case THAI -> FALLBACK_FONT_TH_ID; + case DEVANAGARI -> FALLBACK_FONT_DEVANAGARI_ID; + case MALAYALAM -> FALLBACK_FONT_MALAYALAM_ID; + case TIBETAN -> FALLBACK_FONT_TIBETAN_ID; default -> FALLBACK_FONT_ID; }; } diff --git a/app/core/src/main/java/stirling/software/SPDF/service/SharedSignatureService.java b/app/core/src/main/java/stirling/software/SPDF/service/SharedSignatureService.java index 6c349581d..7043118a4 100644 --- a/app/core/src/main/java/stirling/software/SPDF/service/SharedSignatureService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/SharedSignatureService.java @@ -179,7 +179,7 @@ public class SharedSignatureService { StandardOpenOption.TRUNCATE_EXISTING); // Store reference to image file - response.setDataUrl("/api/v1/general/sign/" + imageFileName); + response.setDataUrl("/api/v1/general/signatures/" + imageFileName); } log.info("Saved signature {} for user {}", request.getId(), username); @@ -207,7 +207,7 @@ public class SharedSignatureService { sig.setLabel(id); // Use ID as label sig.setType("image"); // Default type sig.setScope("personal"); - sig.setDataUrl("/api/v1/general/sign/" + fileName); + sig.setDataUrl("/api/v1/general/signatures/" + fileName); sig.setCreatedAt( Files.getLastModifiedTime(path).toMillis()); sig.setUpdatedAt( @@ -238,7 +238,7 @@ public class SharedSignatureService { sig.setLabel(id); // Use ID as label sig.setType("image"); // Default type sig.setScope("shared"); - sig.setDataUrl("/api/v1/general/sign/" + fileName); + sig.setDataUrl("/api/v1/general/signatures/" + fileName); sig.setCreatedAt( Files.getLastModifiedTime(path).toMillis()); sig.setUpdatedAt( diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/JobOwnershipServiceImpl.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/JobOwnershipServiceImpl.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/JobOwnershipServiceImpl.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/JobOwnershipServiceImpl.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/NoOpJobOwnershipService.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/NoOpJobOwnershipService.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/NoOpJobOwnershipService.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/NoOpJobOwnershipService.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonFontService.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonFontService.java similarity index 93% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonFontService.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonFontService.java index 1a9f7f698..6a56bad09 100644 --- a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonFontService.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonFontService.java @@ -5,7 +5,6 @@ import java.nio.file.Files; import java.util.Base64; import java.util.Locale; -import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import jakarta.annotation.PostConstruct; @@ -25,22 +24,16 @@ import stirling.software.common.util.TempFileManager; public class PdfJsonFontService { private final TempFileManager tempFileManager; + private final stirling.software.common.model.ApplicationProperties applicationProperties; - @Getter - @Value("${stirling.pdf.json.cff-converter.enabled:true}") - private boolean cffConversionEnabled; + @Getter private boolean cffConversionEnabled; - @Getter - @Value("${stirling.pdf.json.cff-converter.method:python}") - private String cffConverterMethod; + @Getter private String cffConverterMethod; - @Value("${stirling.pdf.json.cff-converter.python-command:/opt/venv/bin/python3}") private String pythonCommand; - @Value("${stirling.pdf.json.cff-converter.python-script:/scripts/convert_cff_to_ttf.py}") private String pythonScript; - @Value("${stirling.pdf.json.cff-converter.fontforge-command:fontforge}") private String fontforgeCommand; private volatile boolean pythonCffConverterAvailable; @@ -48,6 +41,7 @@ public class PdfJsonFontService { @PostConstruct private void initialiseCffConverterAvailability() { + loadConfiguration(); if (!cffConversionEnabled) { log.warn("[FONT-DEBUG] CFF conversion is DISABLED in configuration"); pythonCffConverterAvailable = false; @@ -77,6 +71,22 @@ public class PdfJsonFontService { log.info("[FONT-DEBUG] Selected CFF converter method: {}", cffConverterMethod); } + private void loadConfiguration() { + if (applicationProperties.getPdfEditor() != null + && applicationProperties.getPdfEditor().getCffConverter() != null) { + var cfg = applicationProperties.getPdfEditor().getCffConverter(); + this.cffConversionEnabled = cfg.isEnabled(); + this.cffConverterMethod = cfg.getMethod(); + this.pythonCommand = cfg.getPythonCommand(); + this.pythonScript = cfg.getPythonScript(); + this.fontforgeCommand = cfg.getFontforgeCommand(); + } else { + // Use defaults when config is not available + this.cffConversionEnabled = false; + log.warn("[FONT-DEBUG] PdfEditor configuration not available, CFF conversion disabled"); + } + } + public byte[] convertCffProgramToTrueType(byte[] fontBytes, String toUnicode) { if (!cffConversionEnabled || fontBytes == null || fontBytes.length == 0) { log.warn( diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonImageService.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonImageService.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonImageService.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonImageService.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonMetadataService.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonMetadataService.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonMetadataService.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfJsonMetadataService.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingService.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingService.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingService.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/PdfLazyLoadingService.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3ConversionRequest.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3ConversionRequest.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3ConversionRequest.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3ConversionRequest.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3ConversionStrategy.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3ConversionStrategy.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3ConversionStrategy.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3ConversionStrategy.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3FontConversionService.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3FontConversionService.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3FontConversionService.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3FontConversionService.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3FontSignatureCalculator.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3FontSignatureCalculator.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3FontSignatureCalculator.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3FontSignatureCalculator.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GlyphContext.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GlyphContext.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GlyphContext.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GlyphContext.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GlyphExtractor.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GlyphExtractor.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GlyphExtractor.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GlyphExtractor.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GraphicsEngine.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GraphicsEngine.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GraphicsEngine.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3GraphicsEngine.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3LibraryStrategy.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3LibraryStrategy.java similarity index 85% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3LibraryStrategy.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3LibraryStrategy.java index 4385e5725..b4e8f9d95 100644 --- a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3LibraryStrategy.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/Type3LibraryStrategy.java @@ -2,7 +2,6 @@ package stirling.software.SPDF.service.pdfjson.type3; import java.io.IOException; -import org.springframework.beans.factory.annotation.Value; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; @@ -23,8 +22,8 @@ import stirling.software.SPDF.service.pdfjson.type3.library.Type3FontLibraryPayl public class Type3LibraryStrategy implements Type3ConversionStrategy { private final Type3FontLibrary fontLibrary; + private final stirling.software.common.model.ApplicationProperties applicationProperties; - @Value("${stirling.pdf.json.type3.library.enabled:true}") private boolean enabled; @Override @@ -42,6 +41,19 @@ public class Type3LibraryStrategy implements Type3ConversionStrategy { return enabled && fontLibrary != null && fontLibrary.isLoaded(); } + @jakarta.annotation.PostConstruct + private void loadConfiguration() { + if (applicationProperties.getPdfEditor() != null + && applicationProperties.getPdfEditor().getType3() != null + && applicationProperties.getPdfEditor().getType3().getLibrary() != null) { + var cfg = applicationProperties.getPdfEditor().getType3().getLibrary(); + this.enabled = cfg.isEnabled(); + } else { + this.enabled = false; + log.warn("PdfEditor Type3 library configuration not available, disabled"); + } + } + @Override public PdfJsonFontConversionCandidate convert( Type3ConversionRequest request, Type3GlyphContext context) throws IOException { diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibrary.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibrary.java similarity index 94% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibrary.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibrary.java index 32a6abec2..f00c729a2 100644 --- a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibrary.java +++ b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibrary.java @@ -14,7 +14,6 @@ import java.util.stream.Collectors; import org.apache.pdfbox.cos.COSName; import org.apache.pdfbox.pdmodel.font.PDType3Font; -import org.springframework.beans.factory.annotation.Value; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.stereotype.Component; @@ -34,8 +33,8 @@ public class Type3FontLibrary { private final ObjectMapper objectMapper; private final ResourceLoader resourceLoader; + private final stirling.software.common.model.ApplicationProperties applicationProperties; - @Value("${stirling.pdf.json.type3.library.index:classpath:/type3/library/index.json}") private String indexLocation; private final Map signatureIndex = new ConcurrentHashMap<>(); @@ -44,6 +43,17 @@ public class Type3FontLibrary { @jakarta.annotation.PostConstruct void initialise() { + if (applicationProperties.getPdfEditor() != null + && applicationProperties.getPdfEditor().getType3() != null + && applicationProperties.getPdfEditor().getType3().getLibrary() != null) { + this.indexLocation = + applicationProperties.getPdfEditor().getType3().getLibrary().getIndex(); + } else { + log.warn( + "[TYPE3] PdfEditor Type3 library configuration not available; Type3 library disabled"); + entries = List.of(); + return; + } Resource resource = resourceLoader.getResource(indexLocation); if (!resource.exists()) { log.info("[TYPE3] Library index {} not found; Type3 library disabled", indexLocation); diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryEntry.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryEntry.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryEntry.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryEntry.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryMatch.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryMatch.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryMatch.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryMatch.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryPayload.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryPayload.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryPayload.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/library/Type3FontLibraryPayload.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/model/Type3GlyphOutline.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/model/Type3GlyphOutline.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/model/Type3GlyphOutline.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/model/Type3GlyphOutline.java diff --git a/app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/tool/Type3SignatureTool.java b/app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/tool/Type3SignatureTool.java similarity index 100% rename from app/proprietary/src/main/java/stirling/software/SPDF/service/pdfjson/type3/tool/Type3SignatureTool.java rename to app/core/src/main/java/stirling/software/SPDF/service/pdfjson/type3/tool/Type3SignatureTool.java diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index 5ea28f71e..dffebe1bb 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -12,7 +12,6 @@ security: enableLogin: true # set to 'true' to enable login - csrfDisabled: false # set to 'true' to disable CSRF protection (not recommended for production) loginAttemptCount: 5 # lock user account after 5 tries; when using e.g. Fail2Ban you can deactivate the function with -1 loginResetTimeMinutes: 120 # lock account for 2 hours after x attempts loginMethod: all # Accepts values like 'all' and 'normal'(only Login with Username/Password), 'oauth2'(only Login with OAuth2) or 'saml2'(only Login with SAML2) @@ -59,6 +58,8 @@ security: idpCert: classpath:okta.cert # The certificate your Provider will use to authenticate your app's SAML authentication requests. Provided by your Provider privateKey: classpath:saml-private-key.key # Your private key. Generated from your keypair spCert: classpath:saml-public-cert.crt # Your signing certificate. Generated from your keypair + # IMPORTANT: For SAML setup, download your SP metadata from the BACKEND URL: http://localhost:8080/saml2/service-provider-metadata/{registrationId} + # Do NOT use the frontend dev server URL (localhost:5173) as it will generate incorrect ACS URLs. Always use the backend URL (localhost:8080) for SAML configuration. jwt: # This feature is currently under development and not yet fully supported. Do not use in production. persistence: true # Set to 'true' to enable JWT key store enableKeyRotation: true # Set to 'true' to enable key pair rotation @@ -105,6 +106,11 @@ mail: username: '' # SMTP server username password: '' # SMTP server password from: '' # sender email address + startTlsEnable: true # enable STARTTLS (explicit TLS upgrade after connecting) when supported by the SMTP server + startTlsRequired: false # require STARTTLS; connection fails if the upgrade command is not supported + sslEnable: false # enable SSL/TLS wrapper for implicit TLS (typically used with port 465) + sslTrust: '' # optional trusted host override, e.g. "smtp.example.com" or "*"; defaults to "*" (trust all) when empty + sslCheckServerIdentity: false # enable hostname verification when using SSL/TLS legal: termsAndConditions: https://www.stirling.com/legal/terms-of-service # 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 @@ -122,13 +128,15 @@ system: customHTMLFiles: false # enable to have files placed in /customFiles/templates override the existing template HTML files tessdataDir: /usr/share/tessdata # path to the directory containing the Tessdata files. This setting is relevant for Windows systems. For Windows users, this path should be adjusted to point to the appropriate directory where the Tessdata files are stored. enableAnalytics: null # Master toggle for analytics: set to 'true' to enable all analytics, 'false' to disable all analytics, or leave as 'null' to prompt admin on first launch + enableDesktopInstallSlide: true # Set to 'false' to hide the desktop app installation slide in the onboarding flow enablePosthog: null # Enable PostHog analytics (open-source product analytics): set to 'true' to enable, 'false' to disable, or 'null' to enable by default when analytics is enabled enableScarf: null # Enable Scarf tracking pixel: set to 'true' to enable, 'false' to disable, or 'null' to enable by default when analytics is enabled enableUrlToPDF: false # Set to 'true' to enable URL to PDF, INTERNAL ONLY, known security issues, should not be used externally disableSanitize: false # set to true to disable Sanitize HTML; (can lead to injections in HTML) maxDPI: 500 # Maximum allowed DPI for PDF to image conversion - corsAllowedOrigins: [] # List of allowed origins for CORS (e.g. ['http://localhost:5173', 'https://app.example.com']). Leave empty to disable CORS. - frontendUrl: '' # Base URL for frontend (e.g. 'https://pdf.example.com'). Used for generating invite links in emails. If empty, falls back to backend URL. + corsAllowedOrigins: [] # List of allowed origins for CORS (e.g. ['http://localhost:5173', 'https://app.example.com']). Leave empty to disable CORS. For local development with frontend on port 5173, add 'http://localhost:5173' + backendUrl: '' # Backend base URL for SAML/OAuth/API callbacks (e.g. 'http://localhost:8080' for dev, 'https://api.example.com' for production). REQUIRED for SSO authentication to work correctly. This is where your IdP will send SAML responses and OAuth callbacks. Leave empty to default to 'http://localhost:8080' in development. + frontendUrl: '' # Frontend URL for invite email links (e.g. 'https://app.example.com'). Optional - if not set, will use backendUrl. This is the URL users click in invite emails. serverCertificate: enabled: true # Enable server-side certificate for "Sign with Stirling-PDF" option organizationName: Stirling-PDF # Organization name for generated certificates @@ -174,23 +182,6 @@ system: databaseBackup: cron: '0 0 0 * * ?' # Cron expression for automatic database backups "0 0 0 * * ?" daily at midnight -stirling: - pdf: - fallback-font: classpath:/static/fonts/NotoSans-Regular.ttf # Override to point at a custom fallback font - json: - font-normalization: - enabled: false # IMPORTANT: Disable to preserve ToUnicode CMaps for correct font rendering. Ghostscript strips Unicode mappings from CID fonts. - cff-converter: - enabled: true # Wrap CFF/Type1C fonts as OpenType-CFF for browser compatibility - method: python # Converter method: 'python' (fontTools, recommended - wraps as OTF), 'fontforge' (legacy - converts to TTF, may hang on CID fonts) - python-command: /opt/venv/bin/python3 # Python interpreter path - python-script: /scripts/convert_cff_to_ttf.py # Path to font wrapping script - fontforge-command: fontforge # Override if FontForge is installed under a different name/path - type3: - library: - enabled: true # Match common Type3 fonts against the built-in library of converted programs - index: classpath:/type3/library/index.json # Override to point at a custom index.json (supports http:, file:, classpath:) - ui: appNameNavbar: '' # name displayed on the navigation bar logoStyle: classic # Options: 'classic' (default - classic S icon) or 'modern' (minimalist logo) @@ -219,6 +210,7 @@ processExecutor: weasyPrintSessionLimit: 16 installAppSessionLimit: 1 calibreSessionLimit: 1 + imageMagickSessionLimit: 4 ghostscriptSessionLimit: 8 ocrMyPdfSessionLimit: 2 timeoutMinutes: # Process executor timeout in minutes @@ -228,7 +220,26 @@ processExecutor: weasyPrinttimeoutMinutes: 30 installApptimeoutMinutes: 60 calibretimeoutMinutes: 30 + imageMagickTimeoutMinutes: 30 tesseractTimeoutMinutes: 30 qpdfTimeoutMinutes: 30 ghostscriptTimeoutMinutes: 30 ocrMyPdfTimeoutMinutes: 30 + +pdfEditor: + fallback-font: classpath:/static/fonts/NotoSans-Regular.ttf # Override to point at a custom fallback font + cache: + max-bytes: -1 # Max in-memory cache size in bytes; -1 disables byte cap + max-percent: 20 # Max in-memory cache as % of JVM max; used when max-bytes <= 0 + font-normalization: + enabled: false # IMPORTANT: Disable to preserve ToUnicode CMaps for correct font rendering. Ghostscript strips Unicode mappings from CID fonts. + cff-converter: + enabled: true # Wrap CFF/Type1CFF fonts as OpenType-CFF for browser compatibility + method: python # Converter method: 'python' (fontTools, recommended - wraps as OTF), 'fontforge' (legacy - converts to TTF, may hang on CID fonts) + python-command: /opt/venv/bin/python3 # Python interpreter path + python-script: /scripts/convert_cff_to_ttf.py # Path to font wrapping script + fontforge-command: fontforge # Override if FontForge is installed under a different name/path + type3: + library: + enabled: true # Match common Type3 fonts against the built-in library of converted programs + index: classpath:/type3/library/index.json # Override to point at a custom index.json (supports http:, file:, classpath:) diff --git a/app/core/src/main/resources/static/fonts/NotoSansDevanagari-Regular.ttf b/app/core/src/main/resources/static/fonts/NotoSansDevanagari-Regular.ttf new file mode 100644 index 000000000..9a8c4b105 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/NotoSansDevanagari-Regular.ttf differ diff --git a/app/core/src/main/resources/static/fonts/NotoSansMalayalam-Regular.ttf b/app/core/src/main/resources/static/fonts/NotoSansMalayalam-Regular.ttf new file mode 100644 index 000000000..a5a0fe65a Binary files /dev/null and b/app/core/src/main/resources/static/fonts/NotoSansMalayalam-Regular.ttf differ diff --git a/app/core/src/main/resources/static/fonts/NotoSansTC-Regular.ttf b/app/core/src/main/resources/static/fonts/NotoSansTC-Regular.ttf new file mode 100644 index 000000000..75e2b0806 Binary files /dev/null and b/app/core/src/main/resources/static/fonts/NotoSansTC-Regular.ttf differ diff --git a/app/core/src/main/resources/static/fonts/NotoSerifTibetan-Regular.ttf b/app/core/src/main/resources/static/fonts/NotoSerifTibetan-Regular.ttf new file mode 100644 index 000000000..06b817d5c Binary files /dev/null and b/app/core/src/main/resources/static/fonts/NotoSerifTibetan-Regular.ttf differ diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java index c9f02cd28..5a5eff1f1 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/security/CertSignControllerTest.java @@ -1,9 +1,10 @@ package stirling.software.SPDF.controller.api.security; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.lenient; import java.io.ByteArrayOutputStream; import java.io.InputStream; @@ -107,7 +108,8 @@ class CertSignControllerTest { derCertBytes = baos.toByteArray(); } - when(pdfDocumentFactory.load(any(MultipartFile.class))) + lenient() + .when(pdfDocumentFactory.load(any(MultipartFile.class))) .thenAnswer( invocation -> { MultipartFile file = invocation.getArgument(0); @@ -167,6 +169,31 @@ class CertSignControllerTest { assertTrue(response.getBody().length > 0); } + @Test + void testSignPdfWithMissingPkcs12FileThrowsError() { + MockMultipartFile pdfFile = + new MockMultipartFile( + "fileInput", "test.pdf", MediaType.APPLICATION_PDF_VALUE, pdfBytes); + + SignPDFWithCertRequest request = new SignPDFWithCertRequest(); + request.setFileInput(pdfFile); + request.setCertType("PFX"); + request.setPassword("password"); + request.setShowSignature(false); + request.setReason("test"); + request.setLocation("test"); + request.setName("tester"); + request.setPageNumber(1); + request.setShowLogo(false); + + IllegalArgumentException exception = + assertThrows( + IllegalArgumentException.class, + () -> certSignController.signPDFWithCert(request)); + + assertTrue(exception.getMessage().contains("PKCS12 keystore")); + } + @Test void testSignPdfWithJks() throws Exception { MockMultipartFile pdfFile = diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ProprietaryUIDataController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ProprietaryUIDataController.java index d335eb9c2..e13d807da 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ProprietaryUIDataController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/ProprietaryUIDataController.java @@ -94,6 +94,22 @@ public class ProprietaryUIDataController { this.auditRepository = auditRepository; } + /** + * Get the backend base URL for SAML/OAuth redirects. Uses system.backendUrl from config if set, + * otherwise defaults to http://localhost:8080 + */ + private String getBackendBaseUrl() { + String backendUrl = applicationProperties.getSystem().getBackendUrl(); + + // If backendUrl is configured, use it + if (backendUrl != null && !backendUrl.trim().isEmpty()) { + return backendUrl.trim(); + } + + // For development, default to localhost:8080 (backend port) + return "http://localhost:8080"; + } + @GetMapping("/audit-dashboard") @PreAuthorize("hasRole('ADMIN')") @EnterpriseEndpoint @@ -185,14 +201,17 @@ public class ProprietaryUIDataController { } SAML2 saml2 = securityProps.getSaml2(); - if (securityProps.isSaml2Active() - && applicationProperties.getSystem().getEnableAlphaFunctionality() - && applicationProperties.getPremium().isEnabled()) { + if (securityProps.isSaml2Active() && applicationProperties.getPremium().isEnabled()) { String samlIdp = saml2.getProvider(); String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId(); + // For SAML, we need to use the backend URL directly, not a relative path + // This ensures Spring Security generates the correct ACS URL + String backendUrl = getBackendBaseUrl(); + String fullSamlPath = backendUrl + saml2AuthenticationPath; + if (!applicationProperties.getPremium().getProFeatures().isSsoAutoLogin()) { - providerList.put(saml2AuthenticationPath, samlIdp + " (SAML 2)"); + providerList.put(fullSamlPath, samlIdp + " (SAML 2)"); } } @@ -205,6 +224,10 @@ public class ProprietaryUIDataController { data.setLoginMethod(securityProps.getLoginMethod()); data.setAltLogin(!providerList.isEmpty() && securityProps.isAltLogin()); + // Add language configuration for login page + data.setLanguages(applicationProperties.getUi().getLanguages()); + data.setDefaultLocale(applicationProperties.getSystem().getDefaultLocale()); + return ResponseEntity.ok(data); } @@ -328,6 +351,7 @@ public class ProprietaryUIDataController { data.setGrandfatheredUserCount(grandfatheredCount); data.setLicenseMaxUsers(licenseMaxUsers); data.setPremiumEnabled(premiumEnabled); + data.setMailEnabled(applicationProperties.getMail().isEnabled()); return ResponseEntity.ok(data); } @@ -376,7 +400,7 @@ public class ProprietaryUIDataController { data.setUsername(username); data.setRole(user.get().getRolesAsString()); data.setSettings(settingsJson); - data.setChangeCredsFlag(user.get().isFirstLogin()); + data.setChangeCredsFlag(user.get().isFirstLogin() || user.get().isForcePasswordChange()); data.setOAuth2Login(isOAuth2Login); data.setSaml2Login(isSaml2Login); @@ -491,6 +515,8 @@ public class ProprietaryUIDataController { private boolean altLogin; private boolean firstTimeSetup; private boolean showDefaultCredentials; + private List languages; + private String defaultLocale; } @Data @@ -510,6 +536,7 @@ public class ProprietaryUIDataController { private int grandfatheredUserCount; private int licenseMaxUsers; private boolean premiumEnabled; + private boolean mailEnabled; } @Data diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/SignatureController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/SignatureController.java index f42b23a97..53b3e1f38 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/SignatureController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/api/SignatureController.java @@ -1,7 +1,12 @@ package stirling.software.proprietary.controller.api; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.List; +import java.util.Map; +import java.util.stream.Stream; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -17,7 +22,7 @@ import org.springframework.web.bind.annotation.RestController; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import stirling.software.common.annotations.api.UserApi; +import stirling.software.common.configuration.InstallationPathConfig; import stirling.software.proprietary.model.api.signature.SavedSignatureRequest; import stirling.software.proprietary.model.api.signature.SavedSignatureResponse; import stirling.software.proprietary.security.service.UserService; @@ -28,7 +33,6 @@ import stirling.software.proprietary.service.SignatureService; * authentication and enforces per-user storage limits. All endpoints require authentication * via @PreAuthorize("isAuthenticated()"). */ -@UserApi @Slf4j @RestController @RequestMapping("/api/v1/proprietary/signatures") @@ -38,6 +42,7 @@ public class SignatureController { private final SignatureService signatureService; private final UserService userService; + private static final String ALL_USERS_FOLDER = "ALL_USERS"; /** * Save a new signature for the authenticated user. Enforces storage limits and authentication @@ -84,19 +89,105 @@ public class SignatureController { } /** - * Delete a signature owned by the authenticated user. Users can only delete their own personal - * signatures, not shared ones. + * Update a signature label. Users can update labels for their own personal signatures and for + * shared signatures. + */ + @PostMapping("/{signatureId}/label") + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") + public ResponseEntity updateSignatureLabel( + @PathVariable String signatureId, @RequestBody Map body) { + try { + String username = userService.getCurrentUsername(); + String newLabel = body.get("label"); + + if (newLabel == null || newLabel.trim().isEmpty()) { + log.warn("Invalid label update request"); + return ResponseEntity.badRequest().build(); + } + + signatureService.updateSignatureLabel(username, signatureId, newLabel); + log.info("User {} updated label for signature {}", username, signatureId); + return ResponseEntity.noContent().build(); + } catch (IOException e) { + log.warn("Failed to update signature label: {}", e.getMessage()); + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } + } + + /** + * Delete a signature owned by the authenticated user. Users can delete their own personal + * signatures. Admins can also delete shared signatures. */ @DeleteMapping("/{signatureId}") + @PreAuthorize("!hasAuthority('ROLE_DEMO_USER')") public ResponseEntity deleteSignature(@PathVariable String signatureId) { try { String username = userService.getCurrentUsername(); - signatureService.deleteSignature(username, signatureId); - log.info("User {} deleted signature {}", username, signatureId); - return ResponseEntity.noContent().build(); + boolean isAdmin = userService.isCurrentUserAdmin(); + + // Validate filename to prevent path traversal + if (signatureId.contains("..") + || signatureId.contains("/") + || signatureId.contains("\\")) { + log.warn("Invalid signature ID: {}", signatureId); + return ResponseEntity.badRequest().build(); + } + + // Try to delete from personal folder first + try { + signatureService.deleteSignature(username, signatureId); + log.info("User {} deleted personal signature {}", username, signatureId); + return ResponseEntity.noContent().build(); + } catch (IOException e) { + // If not found in personal folder, check if it's in shared folder + if (isAdmin) { + // Admin can delete from shared folder + if (deleteFromSharedFolder(signatureId)) { + log.info("Admin {} deleted shared signature {}", username, signatureId); + return ResponseEntity.noContent().build(); + } + } + // If not admin or not found in shared folder either, return 404 + throw e; + } } catch (IOException e) { log.warn("Failed to delete signature {} for user: {}", signatureId, e.getMessage()); return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); } } + + /** + * Delete a signature from the shared (ALL_USERS) folder. Only admins should call this method. + */ + private boolean deleteFromSharedFolder(String signatureId) throws IOException { + String signatureBasePath = InstallationPathConfig.getSignaturesPath(); + Path sharedFolder = Paths.get(signatureBasePath, ALL_USERS_FOLDER); + boolean deleted = false; + + if (Files.exists(sharedFolder)) { + try (Stream stream = Files.list(sharedFolder)) { + List matchingFiles = + stream.filter( + path -> + path.getFileName() + .toString() + .startsWith(signatureId + ".")) + .toList(); + for (Path file : matchingFiles) { + Files.delete(file); + deleted = true; + log.info("Deleted shared signature file: {}", file); + } + } + + // Also delete metadata file if it exists + Path metadataPath = sharedFolder.resolve(signatureId + ".json"); + if (Files.exists(metadataPath)) { + Files.delete(metadataPath); + log.info("Deleted shared signature metadata: {}", metadataPath); + } + } + + return deleted; + } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java index d035dbc58..17857fc85 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/config/AccountWebController.java @@ -120,9 +120,7 @@ public class AccountWebController { SAML2 saml2 = securityProps.getSaml2(); - if (securityProps.isSaml2Active() - && applicationProperties.getSystem().getEnableAlphaFunctionality() - && applicationProperties.getPremium().isEnabled()) { + if (securityProps.isSaml2Active() && applicationProperties.getPremium().isEnabled()) { String samlIdp = saml2.getProvider(); String saml2AuthenticationPath = "/saml2/authenticate/" + saml2.getRegistrationId(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/MailConfig.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/MailConfig.java index 6a565cade..6538b7ee9 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/MailConfig.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/MailConfig.java @@ -33,7 +33,8 @@ public class MailConfig { // Creates a new instance of JavaMailSenderImpl, which is a Spring implementation JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); - mailSender.setHost(mailProperties.getHost()); + String host = mailProperties.getHost(); + mailSender.setHost(host); mailSender.setPort(mailProperties.getPort()); mailSender.setDefaultEncoding("UTF-8"); @@ -70,8 +71,32 @@ public class MailConfig { log.info("SMTP authentication disabled - no credentials provided"); } + boolean startTlsEnabled = + mailProperties.getStartTlsEnable() == null || mailProperties.getStartTlsEnable(); // Enables STARTTLS to encrypt the connection if supported by the SMTP server - props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.starttls.enable", Boolean.toString(startTlsEnabled)); + if (mailProperties.getStartTlsRequired() != null) { + props.put( + "mail.smtp.starttls.required", mailProperties.getStartTlsRequired().toString()); + } + + if (mailProperties.getSslEnable() != null) { + props.put("mail.smtp.ssl.enable", mailProperties.getSslEnable().toString()); + } + + // Trust the configured host to allow STARTTLS with self-signed certificates + String sslTrust = mailProperties.getSslTrust(); + if (sslTrust == null || sslTrust.trim().isEmpty()) { + sslTrust = "*"; + } + if (sslTrust != null && !sslTrust.trim().isEmpty()) { + props.put("mail.smtp.ssl.trust", sslTrust); + } + if (mailProperties.getSslCheckServerIdentity() != null) { + props.put( + "mail.smtp.ssl.checkserveridentity", + mailProperties.getSslCheckServerIdentity().toString()); + } // Returns the configured mail sender, ready to send emails return mailSender; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index ab1e4934d..1226237c8 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -1,7 +1,6 @@ package stirling.software.proprietary.security.configuration; import java.util.List; -import java.util.Optional; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -25,8 +24,6 @@ import org.springframework.security.saml2.provider.service.web.authentication.Op import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; -import org.springframework.security.web.csrf.CookieCsrfTokenRepository; -import org.springframework.security.web.csrf.CsrfTokenRequestAttributeHandler; import org.springframework.security.web.savedrequest.NullRequestCache; import org.springframework.security.web.servlet.util.matcher.PathPatternRequestMatcher; import org.springframework.web.cors.CorsConfiguration; @@ -47,7 +44,6 @@ import stirling.software.proprietary.security.database.repository.PersistentLogi import stirling.software.proprietary.security.filter.IPRateLimitingFilter; import stirling.software.proprietary.security.filter.JwtAuthenticationFilter; import stirling.software.proprietary.security.filter.UserAuthenticationFilter; -import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.oauth2.CustomOAuth2AuthenticationFailureHandler; import stirling.software.proprietary.security.oauth2.CustomOAuth2AuthenticationSuccessHandler; import stirling.software.proprietary.security.saml2.CustomSaml2AuthenticationFailureHandler; @@ -198,74 +194,19 @@ public class SecurityConfiguration { http.cors(cors -> cors.disable()); } - if (securityProperties.getCsrfDisabled() || !loginEnabledValue) { - http.csrf(CsrfConfigurer::disable); - } + http.csrf(CsrfConfigurer::disable); if (loginEnabledValue) { - boolean v2Enabled = appConfig.v2Enabled(); http.addFilterBefore( userAuthenticationFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(rateLimitingFilter, UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtAuthenticationFilter, UserAuthenticationFilter.class); - if (!securityProperties.getCsrfDisabled()) { - CookieCsrfTokenRepository cookieRepo = - CookieCsrfTokenRepository.withHttpOnlyFalse(); - CsrfTokenRequestAttributeHandler requestHandler = - new CsrfTokenRequestAttributeHandler(); - requestHandler.setCsrfRequestAttributeName(null); - http.csrf( - csrf -> - csrf.ignoringRequestMatchers( - request -> { - String uri = request.getRequestURI(); - - // Ignore CSRF for auth endpoints - if (uri.startsWith("/api/v1/auth/")) { - return true; - } - - String apiKey = request.getHeader("X-API-KEY"); - // If there's no API key, don't ignore CSRF - // (return false) - if (apiKey == null || apiKey.trim().isEmpty()) { - return false; - } - // Validate API key using existing UserService - try { - Optional user = - userService.getUserByApiKey(apiKey); - // If API key is valid, ignore CSRF (return - // true) - // If API key is invalid, don't ignore CSRF - // (return false) - return user.isPresent(); - } catch (Exception e) { - // If there's any error validating the API - // key, don't ignore CSRF - return false; - } - }) - .csrfTokenRepository(cookieRepo) - .csrfTokenRequestHandler(requestHandler)); - } - http.sessionManagement( - sessionManagement -> { - if (v2Enabled) { + sessionManagement -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS); - } else { - sessionManagement - .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) - .maximumSessions(10) - .maxSessionsPreventsLogin(false) - .sessionRegistry(sessionRegistry) - .expiredUrl("/login?logout=true"); - } - }); + SessionCreationPolicy.STATELESS)); http.authenticationProvider(daoAuthenticationProvider()); http.requestCache(requestCache -> requestCache.requestCache(new NullRequestCache())); @@ -348,18 +289,7 @@ public class SecurityConfiguration { if (securityProperties.isOauth2Active()) { http.oauth2Login( oauth2 -> { - // v1: Use /oauth2 as login page for Thymeleaf templates - if (!v2Enabled) { - oauth2.loginPage("/oauth2"); - } - - // v2: Don't set loginPage, let default OAuth2 flow handle it - oauth2 - /* - This Custom handler is used to check if the OAUTH2 user trying to log in, already exists in the database. - If user exists, login proceeds as usual. If user does not exist, then it is auto-created but only if 'OAUTH2AutoCreateUser' - is set as true, else login fails with an error message advising the same. - */ + oauth2.loginPage("/login") .successHandler( new CustomOAuth2AuthenticationSuccessHandler( loginAttemptService, @@ -393,12 +323,8 @@ public class SecurityConfiguration { .saml2Login( saml2 -> { try { - // Only set login page for v1/Thymeleaf mode - if (!v2Enabled) { - saml2.loginPage("/saml2"); - } - - saml2.relyingPartyRegistrationRepository( + saml2.loginPage("/login") + .relyingPartyRegistrationRepository( saml2RelyingPartyRegistrations) .authenticationManager( new ProviderManager(authenticationProvider)) @@ -408,7 +334,8 @@ public class SecurityConfiguration { securityProperties.getSaml2(), userService, jwtService, - licenseSettingsService)) + licenseSettingsService, + applicationProperties)) .failureHandler( new CustomSaml2AuthenticationFailureHandler()) .authenticationRequestResolver( diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AuthController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AuthController.java index de6428554..c3e11c3ab 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AuthController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/AuthController.java @@ -244,10 +244,13 @@ public class AuthController { userMap.put("username", user.getUsername()); userMap.put("role", user.getRolesAsString()); userMap.put("enabled", user.isEnabled()); + userMap.put( + "authenticationType", + user.getAuthenticationType()); // Expose authentication type for SSO detection // Add metadata for OAuth compatibility Map appMetadata = new HashMap<>(); - appMetadata.put("provider", user.getAuthenticationType()); // Default to email provider + appMetadata.put("provider", user.getAuthenticationType()); userMap.put("app_metadata", appMetadata); return userMap; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java index f2b3fd510..6a18aeff0 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/controller/api/UserController.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.UUID; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,6 +19,7 @@ import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.web.bind.annotation.*; +import jakarta.mail.MessagingException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.transaction.Transactional; @@ -236,6 +238,8 @@ public class UserController { return ResponseEntity.status(HttpStatus.UNAUTHORIZED) .body(Map.of("error", "incorrectPassword", "message", "Incorrect password")); } + // Set flags before changing password so they're saved together + user.setForcePasswordChange(false); userService.changePassword(user, newPassword); userService.changeFirstUse(user, false); // Logout using Spring's utility @@ -584,6 +588,79 @@ public class UserController { return ResponseEntity.ok(Map.of("message", "User role updated successfully")); } + @PreAuthorize("hasRole('ROLE_ADMIN')") + @PostMapping("/admin/changePasswordForUser") + public ResponseEntity changePasswordForUser( + @RequestParam(name = "username") String username, + @RequestParam(name = "newPassword", required = false) String newPassword, + @RequestParam(name = "generateRandom", defaultValue = "false") boolean generateRandom, + @RequestParam(name = "sendEmail", defaultValue = "false") boolean sendEmail, + @RequestParam(name = "includePassword", defaultValue = "false") boolean includePassword, + @RequestParam(name = "forcePasswordChange", defaultValue = "false") + boolean forcePasswordChange, + HttpServletRequest request, + Authentication authentication) + throws SQLException, UnsupportedProviderException, MessagingException { + Optional userOpt = userService.findByUsernameIgnoreCase(username); + if (userOpt.isEmpty()) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(Map.of("error", "User not found.")); + } + + String currentUsername = authentication.getName(); + if (currentUsername.equalsIgnoreCase(username)) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(Map.of("error", "Cannot change your own password.")); + } + + User user = userOpt.get(); + + String finalPassword = newPassword; + if (generateRandom) { + finalPassword = UUID.randomUUID().toString().replace("-", "").substring(0, 12); + } + + if (finalPassword == null || finalPassword.trim().isEmpty()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(Map.of("error", "New password is required.")); + } + + // Set force password change flag before changing password so both are saved together + user.setForcePasswordChange(forcePasswordChange); + userService.changePassword(user, finalPassword); + + // Invalidate all active sessions to force reauthentication + userService.invalidateUserSessions(username); + + if (sendEmail) { + if (emailService.isEmpty() || !applicationProperties.getMail().isEnabled()) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body(Map.of("error", "Email is not configured.")); + } + + String userEmail = user.getUsername(); + // Check if username is a valid email format + if (userEmail == null || userEmail.isBlank() || !userEmail.contains("@")) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST) + .body( + Map.of( + "error", + "User's email is not a valid email address. Notifications are disabled.")); + } + + String loginUrl = buildLoginUrl(request); + emailService + .get() + .sendPasswordChangedNotification( + userEmail, + user.getUsername(), + includePassword ? finalPassword : null, + loginUrl); + } + + return ResponseEntity.ok(Map.of("message", "User password updated successfully")); + } + @PreAuthorize("hasRole('ROLE_ADMIN')") @PostMapping("/admin/changeUserEnabled/{username}") public ResponseEntity changeUserEnabled( diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java index b481da51c..a2c3381f1 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/JwtAuthenticationFilter.java @@ -26,6 +26,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import stirling.software.common.model.ApplicationProperties; @@ -39,6 +40,7 @@ import stirling.software.proprietary.security.service.JwtServiceInterface; import stirling.software.proprietary.security.service.UserService; @Slf4j +@RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtServiceInterface jwtService; @@ -47,19 +49,6 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationEntryPoint authenticationEntryPoint; private final ApplicationProperties.Security securityProperties; - public JwtAuthenticationFilter( - JwtServiceInterface jwtService, - UserService userService, - CustomUserDetailsService userDetailsService, - AuthenticationEntryPoint authenticationEntryPoint, - ApplicationProperties.Security securityProperties) { - this.jwtService = jwtService; - this.userService = userService; - this.userDetailsService = userDetailsService; - this.authenticationEntryPoint = authenticationEntryPoint; - this.securityProperties = securityProperties; - } - @Override protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) @@ -68,7 +57,11 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); return; } - if (isStaticResource(request.getContextPath(), request.getRequestURI())) { + + String requestURI = request.getRequestURI(); + String contextPath = request.getContextPath(); + + if (isStaticResource(contextPath, requestURI)) { filterChain.doFilter(request, response); return; } @@ -77,10 +70,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { String jwtToken = jwtService.extractToken(request); if (jwtToken == null) { - // Allow specific auth endpoints to pass through without JWT - String requestURI = request.getRequestURI(); - String contextPath = request.getContextPath(); - + // Allow auth endpoints to pass through without JWT if (!isPublicAuthEndpoint(requestURI, contextPath)) { // For API requests, return 401 JSON String acceptHeader = request.getHeader("Accept"); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java index fe57b3997..182b4cbfe 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/filter/UserAuthenticationFilter.java @@ -241,24 +241,6 @@ public class UserAuthenticationFilter extends OncePerRequestFilter { filterChain.doFilter(request, response); } - private static boolean isPublicAuthEndpoint(String requestURI, String contextPath) { - // Remove context path from URI to normalize path matching - String trimmedUri = - requestURI.startsWith(contextPath) - ? requestURI.substring(contextPath.length()) - : requestURI; - - // Public auth endpoints that don't require authentication - return trimmedUri.startsWith("/login") - || trimmedUri.startsWith("/auth/") - || trimmedUri.startsWith("/oauth2") - || trimmedUri.startsWith("/saml2") - || trimmedUri.startsWith("/api/v1/auth/login") - || trimmedUri.startsWith("/api/v1/auth/refresh") - || trimmedUri.startsWith("/api/v1/auth/logout") - || trimmedUri.startsWith("/api/v1/proprietary/ui-data/login"); - } - private enum UserLoginType { USERDETAILS("UserDetails"), OAUTH2USER("OAuth2User"), diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java index 8207eae28..c53893a8b 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java @@ -59,6 +59,9 @@ public class User implements UserDetails, Serializable { @Column(name = "hasCompletedInitialSetup") private Boolean hasCompletedInitialSetup = false; + @Column(name = "forcePasswordChange") + private Boolean forcePasswordChange = false; + @Column(name = "roleName") private String roleName; @@ -117,6 +120,14 @@ public class User implements UserDetails, Serializable { this.hasCompletedInitialSetup = hasCompletedInitialSetup; } + public boolean isForcePasswordChange() { + return forcePasswordChange != null && forcePasswordChange; + } + + public void setForcePasswordChange(boolean forcePasswordChange) { + this.forcePasswordChange = forcePasswordChange; + } + public void setAuthenticationType(AuthenticationType authenticationType) { this.authenticationType = authenticationType.toString().toLowerCase(); } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java index e1e670394..793c6b62f 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/CustomOAuth2AuthenticationSuccessHandler.java @@ -27,6 +27,7 @@ import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpSession; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.exception.UnsupportedProviderException; @@ -39,6 +40,7 @@ import stirling.software.proprietary.security.service.JwtServiceInterface; import stirling.software.proprietary.security.service.LoginAttemptService; import stirling.software.proprietary.security.service.UserService; +@Slf4j @RequiredArgsConstructor public class CustomOAuth2AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler { @@ -77,12 +79,18 @@ public class CustomOAuth2AuthenticationSuccessHandler if (user != null && !licenseSettingsService.isOAuthEligible(user)) { // User is not grandfathered and no paid license - block OAuth login + log.warn( + "OAuth login blocked for existing user '{}' - not eligible (not grandfathered and no paid license)", + username); response.sendRedirect( request.getContextPath() + "/logout?oAuth2RequiresLicense=true"); return; } } else if (!licenseSettingsService.isOAuthEligible(null)) { // No existing user and no paid license -> block auto creation + log.warn( + "OAuth login blocked for new user '{}' - not eligible (no paid license for auto-creation)", + username); response.sendRedirect(request.getContextPath() + "/logout?oAuth2RequiresLicense=true"); return; } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/OAuth2Configuration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/OAuth2Configuration.java index a053c1ead..2d5f94620 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/OAuth2Configuration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/oauth2/OAuth2Configuration.java @@ -67,10 +67,15 @@ public class OAuth2Configuration { keycloakClientRegistration().ifPresent(registrations::add); if (registrations.isEmpty()) { - log.error("No OAuth2 provider registered"); + log.error("No OAuth2 provider registered - check your OAuth2 configuration"); throw new NoProviderFoundException("At least one OAuth2 provider must be configured."); } + log.info( + "OAuth2 ClientRegistrationRepository created with {} provider(s): {}", + registrations.size(), + registrations.stream().map(ClientRegistration::getRegistrationId).toList()); + return new InMemoryClientRegistrationRepository(registrations); } @@ -165,7 +170,6 @@ public class OAuth2Configuration { githubClient.getUseAsUsername()); boolean isValid = validateProvider(github); - log.info("Initialised GitHub OAuth2 provider"); return isValid ? Optional.of( @@ -208,7 +212,19 @@ public class OAuth2Configuration { null, null); - return !isStringEmpty(oidcProvider.getIssuer()) || validateProvider(oidcProvider) + boolean isValid = + !isStringEmpty(oidcProvider.getIssuer()) || validateProvider(oidcProvider); + if (isValid) { + log.info( + "Initialised OIDC OAuth2 provider: registrationId='{}', issuer='{}', redirectUri='{}'", + name, + oauth.getIssuer(), + REDIRECT_URI_PATH + name); + } else { + log.warn("OIDC OAuth2 provider validation failed - provider will not be registered"); + } + + return isValid ? Optional.of( ClientRegistrations.fromIssuerLocation(oauth.getIssuer()) .registrationId(name) @@ -217,7 +233,7 @@ public class OAuth2Configuration { .scope(oidcProvider.getScopes()) .userNameAttributeName(oidcProvider.getUseAsUsername().getName()) .clientName(clientName) - .redirectUri(REDIRECT_URI_PATH + "oidc") + .redirectUri(REDIRECT_URI_PATH + name) .authorizationGrantType(AUTHORIZATION_CODE) .build()) : Optional.empty(); diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java index 0f350d7b4..3c63f1bf4 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/CustomSaml2AuthenticationSuccessHandler.java @@ -51,6 +51,7 @@ public class CustomSaml2AuthenticationSuccessHandler private final JwtServiceInterface jwtService; private final stirling.software.proprietary.service.UserLicenseSettingsService licenseSettingsService; + private final ApplicationProperties applicationProperties; @Override @Audited(type = AuditEventType.USER_LOGIN, level = AuditLevel.BASIC) @@ -67,21 +68,27 @@ public class CustomSaml2AuthenticationSuccessHandler boolean userExists = userService.usernameExistsIgnoreCase(username); - // Check if user is eligible for SAML (grandfathered or system has paid license) + // Check if user is eligible for SAML (grandfathered or system has ENTERPRISE license) if (userExists) { stirling.software.proprietary.security.model.User user = userService.findByUsernameIgnoreCase(username).orElse(null); - if (user != null && !licenseSettingsService.isOAuthEligible(user)) { - // User is not grandfathered and no paid license - block SAML login - response.sendRedirect( - request.getContextPath() + "/logout?saml2RequiresLicense=true"); + if (user != null && !licenseSettingsService.isSamlEligible(user)) { + // User is not grandfathered and no ENTERPRISE license - block SAML login + log.warn( + "SAML2 login blocked for existing user '{}' - not eligible (not grandfathered and no ENTERPRISE license)", + username); + String origin = resolveOrigin(request); + response.sendRedirect(origin + "/logout?saml2RequiresLicense=true"); return; } - } else if (!licenseSettingsService.isOAuthEligible(null)) { - // No existing user and no paid license -> block auto creation - response.sendRedirect( - request.getContextPath() + "/logout?saml2RequiresLicense=true"); + } else if (!licenseSettingsService.isSamlEligible(null)) { + // No existing user and no ENTERPRISE license -> block auto creation + log.warn( + "SAML2 login blocked for new user '{}' - not eligible (no ENTERPRISE license for auto-creation)", + username); + String origin = resolveOrigin(request); + response.sendRedirect(origin + "/logout?saml2RequiresLicense=true"); return; } @@ -138,20 +145,28 @@ public class CustomSaml2AuthenticationSuccessHandler log.debug( "User {} exists with password but is not SSO user, redirecting to logout", username); - response.sendRedirect( - contextPath + "/logout?oAuth2AuthenticationErrorWeb=true"); + String origin = resolveOrigin(request); + response.sendRedirect(origin + "/logout?oAuth2AuthenticationErrorWeb=true"); return; } try { - if (!userExists || saml2Properties.getBlockRegistration()) { - log.debug("Registration blocked for new user: {}", username); - response.sendRedirect( - contextPath + "/login?errorOAuth=oAuth2AdminBlockedUser"); + // Block new users only if: blockRegistration is true OR autoCreateUser is false + if (!userExists + && (saml2Properties.getBlockRegistration() + || !saml2Properties.getAutoCreateUser())) { + log.debug( + "Registration blocked for new user '{}' (blockRegistration: {}, autoCreateUser: {})", + username, + saml2Properties.getBlockRegistration(), + saml2Properties.getAutoCreateUser()); + String origin = resolveOrigin(request); + response.sendRedirect(origin + "/login?errorOAuth=oAuth2AdminBlockedUser"); return; } if (!userExists && licenseSettingsService.wouldExceedLimit(1)) { - response.sendRedirect(contextPath + "/logout?maxUsersReached=true"); + String origin = resolveOrigin(request); + response.sendRedirect(origin + "/logout?maxUsersReached=true"); return; } @@ -216,16 +231,30 @@ public class CustomSaml2AuthenticationSuccessHandler String contextPath, String jwt) { String redirectPath = resolveRedirectPath(request, contextPath); - String origin = - resolveForwardedOrigin(request) - .orElseGet( - () -> - resolveOriginFromReferer(request) - .orElseGet(() -> buildOriginFromRequest(request))); + String origin = resolveOrigin(request); clearRedirectCookie(response); return origin + redirectPath + "#access_token=" + jwt; } + /** + * Resolve the origin (frontend URL) for redirects. First checks system.frontendUrl from config, + * then falls back to detecting from request headers. + */ + private String resolveOrigin(HttpServletRequest request) { + // First check if frontendUrl is configured + String configuredFrontendUrl = applicationProperties.getSystem().getFrontendUrl(); + if (configuredFrontendUrl != null && !configuredFrontendUrl.trim().isEmpty()) { + return configuredFrontendUrl.trim(); + } + + // Fall back to auto-detection from request headers + return resolveForwardedOrigin(request) + .orElseGet( + () -> + resolveOriginFromReferer(request) + .orElseGet(() -> buildOriginFromRequest(request))); + } + private String resolveRedirectPath(HttpServletRequest request, String contextPath) { return extractRedirectPathFromCookie(request) .filter(path -> path.startsWith("/")) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/Saml2Configuration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/Saml2Configuration.java index 9d21f88a3..99be4b5b0 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/Saml2Configuration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/saml2/Saml2Configuration.java @@ -41,22 +41,74 @@ public class Saml2Configuration { @ConditionalOnProperty(name = "security.saml2.enabled", havingValue = "true") public RelyingPartyRegistrationRepository relyingPartyRegistrations() throws Exception { SAML2 samlConf = applicationProperties.getSecurity().getSaml2(); - X509Certificate idpCert = CertificateUtils.readCertificate(samlConf.getIdpCert()); + + log.info( + "Initializing SAML2 configuration with registration ID: {}", + samlConf.getRegistrationId()); + + // Load IdP certificate + X509Certificate idpCert; + try { + Resource idpCertResource = samlConf.getIdpCert(); + log.info("Loading IdP certificate from: {}", idpCertResource.getDescription()); + if (!idpCertResource.exists()) { + log.error( + "SAML2 IdP certificate not found at: {}", idpCertResource.getDescription()); + throw new IllegalStateException( + "SAML2 IdP certificate file does not exist: " + + idpCertResource.getDescription()); + } + idpCert = CertificateUtils.readCertificate(idpCertResource); + log.info( + "Successfully loaded IdP certificate. Subject: {}", + idpCert.getSubjectX500Principal().getName()); + } catch (Exception e) { + log.error("Failed to load SAML2 IdP certificate: {}", e.getMessage(), e); + throw new IllegalStateException("Failed to load SAML2 IdP certificate", e); + } + Saml2X509Credential verificationCredential = Saml2X509Credential.verification(idpCert); + + // Load SP private key and certificate Resource privateKeyResource = samlConf.getPrivateKey(); Resource certificateResource = samlConf.getSpCert(); - Saml2X509Credential signingCredential = - new Saml2X509Credential( - CertificateUtils.readPrivateKey(privateKeyResource), - CertificateUtils.readCertificate(certificateResource), - Saml2X509CredentialType.SIGNING); + + log.info("Loading SP private key from: {}", privateKeyResource.getDescription()); + if (!privateKeyResource.exists()) { + log.error("SAML2 SP private key not found at: {}", privateKeyResource.getDescription()); + throw new IllegalStateException( + "SAML2 SP private key file does not exist: " + + privateKeyResource.getDescription()); + } + + log.info("Loading SP certificate from: {}", certificateResource.getDescription()); + if (!certificateResource.exists()) { + log.error( + "SAML2 SP certificate not found at: {}", certificateResource.getDescription()); + throw new IllegalStateException( + "SAML2 SP certificate file does not exist: " + + certificateResource.getDescription()); + } + + Saml2X509Credential signingCredential; + try { + signingCredential = + new Saml2X509Credential( + CertificateUtils.readPrivateKey(privateKeyResource), + CertificateUtils.readCertificate(certificateResource), + Saml2X509CredentialType.SIGNING); + log.info("Successfully loaded SP credentials"); + } catch (Exception e) { + log.error("Failed to load SAML2 SP credentials: {}", e.getMessage(), e); + throw new IllegalStateException("Failed to load SAML2 SP credentials", e); + } RelyingPartyRegistration rp = RelyingPartyRegistration.withRegistrationId(samlConf.getRegistrationId()) .signingX509Credentials(c -> c.add(signingCredential)) .entityId(samlConf.getIdpIssuer()) .singleLogoutServiceBinding(Saml2MessageBinding.POST) .singleLogoutServiceLocation(samlConf.getIdpSingleLogoutUrl()) - .singleLogoutServiceResponseLocation("http://localhost:8080/login") + .singleLogoutServiceResponseLocation("{baseUrl}/login") .assertionConsumerServiceBinding(Saml2MessageBinding.POST) .assertionConsumerServiceLocation( "{baseUrl}/login/saml2/sso/{registrationId}") @@ -75,9 +127,14 @@ public class Saml2Configuration { .singleLogoutServiceLocation( samlConf.getIdpSingleLogoutUrl()) .singleLogoutServiceResponseLocation( - "http://localhost:8080/login") + "{baseUrl}/login") .wantAuthnRequestsSigned(true)) .build(); + + log.info( + "SAML2 configuration initialized successfully. Registration ID: {}, IdP: {}", + samlConf.getRegistrationId(), + samlConf.getIdpIssuer()); return new InMemoryRelyingPartyRegistrationRepository(rp); } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/EmailService.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/EmailService.java index 8df76fca3..3ae079d0f 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/service/EmailService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/service/EmailService.java @@ -223,4 +223,54 @@ public class EmailService { sendPlainEmail(to, subject, body, true); } + + @Async + public void sendPasswordChangedNotification( + String to, String username, String newPassword, String loginUrl) + throws MessagingException { + String subject = "Your Stirling PDF password has been updated"; + + String passwordSection = + newPassword == null + ? "" + : """ +
+

Temporary Password: %s

+
+ """ + .formatted(newPassword); + + String body = + """ + +
+
+
+ \"Stirling +
+
+

Your password was changed

+

Hello %s,

+

An administrator has updated the password for your Stirling PDF account.

+ %s +

If you did not expect this change, please contact your administrator immediately.

+ +

Or copy and paste this link in your browser:

+
+ %s +
+
+
+ © 2025 Stirling PDF. All rights reserved. +
+
+
+ + """ + .formatted(username, passwordSection, loginUrl, loginUrl); + + sendPlainEmail(to, subject, body, true); + } } diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/ImageMagickLineArtConversionService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/ImageMagickLineArtConversionService.java new file mode 100644 index 000000000..8ca6a83e7 --- /dev/null +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/ImageMagickLineArtConversionService.java @@ -0,0 +1,81 @@ +package stirling.software.proprietary.service; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import javax.imageio.ImageIO; + +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject; +import org.springframework.stereotype.Service; + +import lombok.extern.slf4j.Slf4j; + +import stirling.software.common.service.LineArtConversionService; +import stirling.software.common.util.ProcessExecutor; +import stirling.software.common.util.ProcessExecutor.ProcessExecutorResult; + +@Slf4j +@Service +public class ImageMagickLineArtConversionService implements LineArtConversionService { + + @Override + public PDImageXObject convertImageToLineArt( + PDDocument doc, PDImageXObject originalImage, double threshold, int edgeLevel) + throws IOException { + + Path inputImage = Files.createTempFile("lineart_image_input_", ".png"); + Path outputImage = Files.createTempFile("lineart_image_output_", ".tiff"); + + try { + ImageIO.write(originalImage.getImage(), "png", inputImage.toFile()); + + List command = new ArrayList<>(); + command.add("magick"); + command.add(inputImage.toString()); + command.add("-colorspace"); + command.add("Gray"); + + // Edge-aware line art conversion using ImageMagick's built-in operators. + // -edge/-negate/-normalize are standard convert options (IM v6+/v7) that + // accentuate outlines before thresholding to a bilevel image. + command.add("-edge"); + command.add(String.valueOf(edgeLevel)); + command.add("-negate"); + command.add("-normalize"); + + command.add("-type"); + command.add("Bilevel"); + command.add("-threshold"); + command.add(String.format(Locale.ROOT, "%.1f%%", threshold)); + command.add("-compress"); + command.add("Group4"); + command.add(outputImage.toString()); + + ProcessExecutorResult result = + ProcessExecutor.getInstance(ProcessExecutor.Processes.IMAGEMAGICK) + .runCommandWithOutputHandling(command); + + if (result.getRc() != 0) { + log.warn( + "ImageMagick line art conversion failed with return code: {}", + result.getRc()); + throw new IOException("ImageMagick line art conversion failed"); + } + + byte[] convertedBytes = Files.readAllBytes(outputImage); + return PDImageXObject.createFromByteArray( + doc, convertedBytes, originalImage.getCOSObject().toString()); + } catch (Exception e) { + log.warn("ImageMagick line art conversion failed", e); + throw new IOException("ImageMagick line art conversion failed", e); + } finally { + Files.deleteIfExists(inputImage); + Files.deleteIfExists(outputImage); + } + } +} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/SignatureService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/SignatureService.java index 6f849ebfe..63ee28512 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/SignatureService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/SignatureService.java @@ -2,6 +2,7 @@ package stirling.software.proprietary.service; import java.io.FileNotFoundException; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -13,6 +14,8 @@ import java.util.stream.Stream; import org.springframework.stereotype.Service; +import com.fasterxml.jackson.databind.ObjectMapper; + import lombok.extern.slf4j.Slf4j; import stirling.software.common.configuration.InstallationPathConfig; @@ -31,6 +34,7 @@ public class SignatureService implements PersonalSignatureServiceInterface { private final String SIGNATURE_BASE_PATH; private final String ALL_USERS_FOLDER = "ALL_USERS"; + private final ObjectMapper objectMapper = new ObjectMapper(); // Storage limits per user private static final int MAX_SIGNATURES_PER_USER = 20; @@ -88,6 +92,14 @@ public class SignatureService implements PersonalSignatureServiceInterface { response.setCreatedAt(timestamp); response.setUpdatedAt(timestamp); + // Copy text signature properties if present + if ("text".equals(request.getType())) { + response.setSignerName(request.getSignerName()); + response.setFontFamily(request.getFontFamily()); + response.setFontSize(request.getFontSize()); + response.setTextColor(request.getTextColor()); + } + // Extract and save image data String dataUrl = request.getDataUrl(); if (dataUrl != null && dataUrl.startsWith("data:image/")) { @@ -133,6 +145,19 @@ public class SignatureService implements PersonalSignatureServiceInterface { response.setDataUrl("/api/v1/general/signatures/" + imageFileName); } + // Save metadata JSON file + String metadataFileName = request.getId() + ".json"; + Path metadataPath = targetFolder.resolve(metadataFileName); + verifyPathWithinDirectory(metadataPath, targetFolder); + + String metadataJson = objectMapper.writeValueAsString(response); + Files.writeString( + metadataPath, + metadataJson, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + log.info("Saved signature {} for user {} (scope: {})", request.getId(), username, scope); return response; } @@ -179,6 +204,13 @@ public class SignatureService implements PersonalSignatureServiceInterface { log.info("Deleted signature file: {}", file); } } + + // Also delete metadata file if it exists + Path metadataPath = personalFolder.resolve(signatureId + ".json"); + if (Files.exists(metadataPath)) { + Files.delete(metadataPath); + log.info("Deleted signature metadata: {}", metadataPath); + } } if (!deleted) { @@ -186,6 +218,50 @@ public class SignatureService implements PersonalSignatureServiceInterface { } } + /** Update a signature label. */ + public void updateSignatureLabel(String username, String signatureId, String newLabel) + throws IOException { + validateFileName(signatureId); + + // Try personal folder first + Path personalFolder = Paths.get(SIGNATURE_BASE_PATH, username); + Path metadataPath = personalFolder.resolve(signatureId + ".json"); + + if (Files.exists(metadataPath)) { + updateMetadataLabel(metadataPath, newLabel); + log.info("Updated label for personal signature {} (user: {})", signatureId, username); + return; + } + + // If not found in personal, try shared folder + Path sharedFolder = Paths.get(SIGNATURE_BASE_PATH, ALL_USERS_FOLDER); + Path sharedMetadataPath = sharedFolder.resolve(signatureId + ".json"); + + if (Files.exists(sharedMetadataPath)) { + updateMetadataLabel(sharedMetadataPath, newLabel); + log.info("Updated label for shared signature {}", signatureId); + return; + } + + throw new FileNotFoundException("Signature metadata not found"); + } + + private void updateMetadataLabel(Path metadataPath, String newLabel) throws IOException { + String metadataJson = Files.readString(metadataPath, StandardCharsets.UTF_8); + SavedSignatureResponse sig = + objectMapper.readValue(metadataJson, SavedSignatureResponse.class); + sig.setLabel(newLabel); + sig.setUpdatedAt(System.currentTimeMillis()); + + String updatedJson = objectMapper.writeValueAsString(sig); + Files.writeString( + metadataPath, + updatedJson, + StandardCharsets.UTF_8, + StandardOpenOption.CREATE, + StandardOpenOption.TRUNCATE_EXISTING); + } + // Private helper methods private void enforceStorageLimits(String username, String dataUrlToAdd) throws IOException { @@ -245,16 +321,31 @@ public class SignatureService implements PersonalSignatureServiceInterface { String fileName = path.getFileName().toString(); String id = fileName.substring(0, fileName.lastIndexOf('.')); - SavedSignatureResponse sig = new SavedSignatureResponse(); - sig.setId(id); - sig.setLabel(id); - sig.setType("image"); - sig.setScope(scope); - sig.setCreatedAt(Files.getLastModifiedTime(path).toMillis()); - sig.setUpdatedAt(Files.getLastModifiedTime(path).toMillis()); + // Try to load metadata from JSON file + Path metadataPath = folder.resolve(id + ".json"); + SavedSignatureResponse sig; - // Set unified URL path (works for both personal and shared) - sig.setDataUrl("/api/v1/general/signatures/" + fileName); + if (Files.exists(metadataPath)) { + // Load from metadata file + String metadataJson = + Files.readString( + metadataPath, StandardCharsets.UTF_8); + sig = + objectMapper.readValue( + metadataJson, SavedSignatureResponse.class); + } else { + // Fallback for old signatures without metadata + sig = new SavedSignatureResponse(); + sig.setId(id); + sig.setLabel(id); + sig.setType("image"); + sig.setScope(scope); + sig.setCreatedAt( + Files.getLastModifiedTime(path).toMillis()); + sig.setUpdatedAt( + Files.getLastModifiedTime(path).toMillis()); + sig.setDataUrl("/api/v1/general/signatures/" + fileName); + } signatures.add(sig); } catch (IOException e) { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java index 1cddefde3..54660a1cc 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/UserLicenseSettingsService.java @@ -21,6 +21,7 @@ import stirling.software.common.model.ApplicationProperties; import stirling.software.proprietary.model.UserLicenseSettings; import stirling.software.proprietary.security.configuration.ee.KeygenLicenseVerifier.License; import stirling.software.proprietary.security.configuration.ee.LicenseKeyChecker; +import stirling.software.proprietary.security.model.User; import stirling.software.proprietary.security.repository.UserLicenseSettingsRepository; import stirling.software.proprietary.security.service.UserService; @@ -176,6 +177,13 @@ public class UserLicenseSettingsService { */ @Transactional public void grandfatherExistingOAuthUsers() { + // Only grandfather users if this is a V1→V2 upgrade, not a fresh V2 install + Boolean isNewServer = applicationProperties.getAutomaticallyGenerated().getIsNewServer(); + if (Boolean.TRUE.equals(isNewServer)) { + log.info("Fresh V2 installation detected - skipping OAuth user grandfathering"); + return; + } + UserLicenseSettings settings = getOrCreateSettings(); // Check if we've already run this migration @@ -343,17 +351,65 @@ public class UserLicenseSettingsService { * @param user The user to check * @return true if the user can use OAuth/SAML */ - public boolean isOAuthEligible(stirling.software.proprietary.security.model.User user) { - // Grandfathered users always have OAuth access - if (user != null && user.isOauthGrandfathered()) { - log.debug("User {} is grandfathered for OAuth", user.getUsername()); + public boolean isOAuthEligible(User user) { + String username = (user != null) ? user.getUsername() : ""; + log.info("OAuth eligibility check for user: {}", username); + + // Check license first - if paying, they're eligible (no need to check grandfathering) + boolean hasPaid = hasPaidLicense(); + if (hasPaid) { + log.debug("User {} eligible for OAuth via paid license", username); return true; } - // Users can use OAuth/SAML only if system has ENTERPRISE license - boolean hasEnterpriseLicense = hasEnterpriseLicense(); - log.debug("OAuth eligibility check: hasEnterpriseLicense={}", hasEnterpriseLicense); - return hasEnterpriseLicense; + // No license - check if grandfathered (fallback for V1 users) + if (user != null && user.isOauthGrandfathered()) { + log.info("User {} eligible for OAuth via grandfathering (no paid license)", username); + return true; + } + + // Not grandfathered and no license + log.info("User {} NOT eligible for OAuth: no paid license and not grandfathered", username); + return false; + } + + /** + * Checks if a user is eligible to use SAML authentication. + * + *

A user is eligible if: + * + *

    + *
  • They are grandfathered for OAuth (existing user before policy change), OR + *
  • The system has an ENTERPRISE license (SAML is enterprise-only) + *
+ * + * @param user The user to check + * @return true if the user can use SAML + */ + public boolean isSamlEligible(User user) { + String username = (user != null) ? user.getUsername() : ""; + log.info("SAML2 eligibility check for user: {}", username); + + // Check license first - if paying, they're eligible (no need to check grandfathering) + boolean hasEnterprise = hasEnterpriseLicense(); + if (hasEnterprise) { + log.debug("User {} eligible for SAML2 via ENTERPRISE license", username); + return true; + } + + // No license - check if grandfathered (fallback for V1 users) + if (user != null && user.isOauthGrandfathered()) { + log.info( + "User {} eligible for SAML2 via grandfathering (no ENTERPRISE license)", + username); + return true; + } + + // Not grandfathered and no license + log.info( + "User {} NOT eligible for SAML2: no ENTERPRISE license and not grandfathered", + username); + return false; } /** @@ -495,8 +551,12 @@ public class UserLicenseSettingsService { if (checker == null) { return false; } + License license = checker.getPremiumLicenseEnabledResult(); - return license == License.SERVER || license == License.ENTERPRISE; + boolean hasPaid = (license == License.SERVER || license == License.ENTERPRISE); + log.info("License check result: type={}, requiresPaid=true, hasPaid={}", license, hasPaid); + + return hasPaid; } /** @@ -510,7 +570,19 @@ public class UserLicenseSettingsService { if (checker == null) { return false; } + License license = checker.getPremiumLicenseEnabledResult(); + log.info( + "License check result: type={}, requiresEnterprise=true, hasEnterprise={}", + license, + (license == License.ENTERPRISE)); + + if (license != License.ENTERPRISE) { + log.warn( + "SAML2 requires ENTERPRISE license but found: {}. SAML2 login will be blocked.", + license); + } + return license == License.ENTERPRISE; } } diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/security/oauth2/OAuth2ConfigurationTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/security/oauth2/OAuth2ConfigurationTest.java new file mode 100644 index 000000000..750696b77 --- /dev/null +++ b/app/proprietary/src/test/java/stirling/software/proprietary/security/oauth2/OAuth2ConfigurationTest.java @@ -0,0 +1,162 @@ +package stirling.software.proprietary.security.oauth2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for OAuth2Configuration redirect URI logic. + * + *

These tests validate the critical fix for GitHub issue #5141: The redirect URI path segment + * MUST match the registration ID. Previously, the redirect URI was hardcoded to 'oidc', causing + * InvalidClientRegistrationIdException when custom provider names were used. + * + *

Note: These are conceptual tests documenting the expected behavior. Full integration testing + * with actual OIDC discovery would require: 1. Mock HTTP server for OIDC discovery endpoints 2. + * Valid OIDC configuration responses 3. Network mocking infrastructure + */ +class OAuth2ConfigurationTest { + + /** + * Tests the redirect URI pattern for OIDC provider configurations. + * + *

Critical behavior (GitHub issue #5141 fix): The redirect URI path segment MUST match the + * registration ID. For example: - Provider name: "authentik" → Redirect URI: + * "/login/oauth2/code/authentik" - Provider name: "mycompany" → Redirect URI: + * "/login/oauth2/code/mycompany" - Provider name: "oidc" → Redirect URI: + * "/login/oauth2/code/oidc" + * + *

Previously, the redirect URI was hardcoded to 'oidc', causing Spring Security to look for + * a registration with ID 'oidc' when the provider redirected back. This caused + * InvalidClientRegistrationIdException when custom provider names were used. + */ + @Test + void testRedirectUriPattern_usesProviderNameNotHardcodedOidc() { + // Verify the redirect URI pattern constant + String redirectUriBase = "{baseUrl}/login/oauth2/code/"; + + // Test cases: provider name → expected redirect URI + String[][] testCases = { + {"authentik", redirectUriBase + "authentik"}, + {"mycompany", redirectUriBase + "mycompany"}, + {"oidc", redirectUriBase + "oidc"}, + {"okta", redirectUriBase + "okta"}, + {"auth0", redirectUriBase + "auth0"} + }; + + for (String[] testCase : testCases) { + String providerName = testCase[0]; + String expectedRedirectUri = testCase[1]; + + // The fix ensures: .redirectUri(REDIRECT_URI_PATH + name) + // instead of: .redirectUri(REDIRECT_URI_PATH + "oidc") + String actualRedirectUri = redirectUriBase + providerName; + + assertEquals( + expectedRedirectUri, + actualRedirectUri, + String.format( + "Redirect URI for provider '%s' must use provider name, not hardcoded 'oidc'", + providerName)); + } + } + + /** + * Documents the critical fix for OAuth2 redirect URI mismatch. + * + *

This test validates the logic that was changed in OAuth2Configuration.java line 220: + * + *

+     * // BEFORE (bug):
+     * .redirectUri(REDIRECT_URI_PATH + "oidc")  // Always "oidc"
+     *
+     * // AFTER (fix):
+     * .redirectUri(REDIRECT_URI_PATH + name)  // Dynamic provider name
+     * 
+ */ + @Test + void testCriticalFix_redirectUriMatchesRegistrationId() { + // The redirect URI path segment extraction by Spring Security + String callbackUrl = "http://localhost:8080/login/oauth2/code/authentik?code=abc123"; + + // Spring extracts the path segment between "code/" and "?" + String extractedRegistrationId = extractRegistrationIdFromCallback(callbackUrl); + + // The extracted ID MUST match an actual registration ID + assertEquals("authentik", extractedRegistrationId); + + // If we had used hardcoded "oidc", the callback would be: + String buggyCallbackUrl = "http://localhost:8080/login/oauth2/code/oidc?code=abc123"; + String buggyExtractedId = extractRegistrationIdFromCallback(buggyCallbackUrl); + + // This would look for registration with ID "oidc" but we registered "authentik" + assertEquals("oidc", buggyExtractedId); + + // The mismatch: registrationId="authentik", but Spring looks for "oidc" + // Result: InvalidClientRegistrationIdException + assertNotNull(buggyExtractedId, "This demonstrates the bug that was fixed"); + } + + /** Helper method simulating Spring's extraction of registration ID from callback URL */ + private String extractRegistrationIdFromCallback(String callbackUrl) { + // Simplified version of what Spring Security does + // Actual: OAuth2AuthorizationRequestRedirectFilter extracts from path + String path = callbackUrl.split("\\?")[0]; + String[] parts = path.split("/"); + return parts[parts.length - 1]; // Last path segment + } + + /** + * Validates the frontend-backend flow for custom provider names. + * + *

Complete flow: 1. Backend: Provider configured as "authentik" in settings.yml 2. Backend: + * ClientRegistration created with registrationId="authentik" 3. Backend: Redirect URI set to + * "{baseUrl}/login/oauth2/code/authentik" 4. Backend: Login endpoint returns providerList with + * "/oauth2/authorization/authentik" 5. Frontend: Extracts "authentik" from path and uses it for + * OAuth login 6. Frontend: Redirects to "/oauth2/authorization/authentik" 7. Backend: Spring + * Security redirects to provider with redirect_uri containing "authentik" 8. Provider: + * Redirects back to "/login/oauth2/code/authentik?code=..." 9. Backend: Spring Security + * extracts "authentik" from callback URL 10. Backend: Looks up ClientRegistration with ID + * "authentik" ✅ SUCCESS + * + *

If redirect URI was hardcoded to "oidc" (the bug): Step 7: Provider redirects to + * "/login/oauth2/code/oidc?code=..." Step 9: Spring Security looks for registration ID "oidc" + * Step 10: FAIL - No registration found with ID "oidc" (we registered "authentik") Result: + * InvalidClientRegistrationIdException + */ + @Test + void testEndToEndFlow_registrationIdConsistency() { + String providerName = "authentik"; + + // Step 2: Registration ID + String registrationId = providerName; + assertEquals("authentik", registrationId); + + // Step 3: Redirect URI (MUST use same name) + String redirectUri = "{baseUrl}/login/oauth2/code/" + providerName; + assertEquals("{baseUrl}/login/oauth2/code/authentik", redirectUri); + + // Step 4: Provider list endpoint + String authorizationPath = "/oauth2/authorization/" + providerName; + assertEquals("/oauth2/authorization/authentik", authorizationPath); + + // Step 5: Frontend extracts provider ID + String frontendProviderId = + authorizationPath.substring(authorizationPath.lastIndexOf('/') + 1); + assertEquals("authentik", frontendProviderId); + + // Step 6-8: OAuth flow (external) + + // Step 9: Callback URL from provider + String callbackUrl = + "http://localhost:8080/login/oauth2/code/" + providerName + "?code=abc123"; + String extractedId = extractRegistrationIdFromCallback(callbackUrl); + + // Step 10: Registration lookup + assertEquals( + registrationId, + extractedId, + "Registration ID from callback MUST match original registration ID"); + } +} diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/security/service/MailConfigTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/security/service/MailConfigTest.java index 3db3493f4..5df56f8ef 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/security/service/MailConfigTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/security/service/MailConfigTest.java @@ -27,6 +27,11 @@ class MailConfigTest { when(mailProps.getPort()).thenReturn(587); when(mailProps.getUsername()).thenReturn("user@example.com"); when(mailProps.getPassword()).thenReturn("password"); + when(mailProps.getStartTlsEnable()).thenReturn(null); + when(mailProps.getStartTlsRequired()).thenReturn(null); + when(mailProps.getSslEnable()).thenReturn(null); + when(mailProps.getSslTrust()).thenReturn(null); + when(mailProps.getSslCheckServerIdentity()).thenReturn(null); } @Test @@ -50,6 +55,32 @@ class MailConfigTest { () -> assertEquals("password", impl.getPassword()), () -> assertEquals("UTF-8", impl.getDefaultEncoding()), () -> assertEquals("true", props.getProperty("mail.smtp.auth")), - () -> assertEquals("true", props.getProperty("mail.smtp.starttls.enable"))); + () -> assertEquals("true", props.getProperty("mail.smtp.starttls.enable")), + () -> assertEquals(null, props.getProperty("mail.smtp.starttls.required")), + () -> assertEquals(null, props.getProperty("mail.smtp.ssl.enable")), + () -> assertEquals("*", props.getProperty("mail.smtp.ssl.trust"))); + } + + @Test + void shouldRespectExplicitTlsOverrides() { + ApplicationProperties appProps = mock(ApplicationProperties.class); + when(mailProps.getStartTlsEnable()).thenReturn(false); + when(mailProps.getStartTlsRequired()).thenReturn(true); + when(mailProps.getSslEnable()).thenReturn(true); + when(mailProps.getSslTrust()).thenReturn("*"); + when(mailProps.getSslCheckServerIdentity()).thenReturn(true); + when(appProps.getMail()).thenReturn(mailProps); + + MailConfig config = new MailConfig(appProps); + JavaMailSenderImpl impl = (JavaMailSenderImpl) config.javaMailSender(); + + Properties props = impl.getJavaMailProperties(); + + assertAll( + () -> assertEquals("false", props.getProperty("mail.smtp.starttls.enable")), + () -> assertEquals("true", props.getProperty("mail.smtp.starttls.required")), + () -> assertEquals("true", props.getProperty("mail.smtp.ssl.enable")), + () -> assertEquals("*", props.getProperty("mail.smtp.ssl.trust")), + () -> assertEquals("true", props.getProperty("mail.smtp.ssl.checkserveridentity"))); } } diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java index 139146d70..a7f8042f6 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/service/UserLicenseSettingsServiceTest.java @@ -33,6 +33,7 @@ class UserLicenseSettingsServiceTest { @Mock private UserService userService; @Mock private ApplicationProperties applicationProperties; @Mock private ApplicationProperties.Premium premium; + @Mock private ApplicationProperties.AutomaticallyGenerated automaticallyGenerated; @Mock private LicenseKeyChecker licenseKeyChecker; @Mock private ObjectProvider licenseKeyCheckerProvider; @@ -49,6 +50,8 @@ class UserLicenseSettingsServiceTest { mockSettings.setGrandfatheredUserSignature("80:test-signature"); when(applicationProperties.getPremium()).thenReturn(premium); + when(applicationProperties.getAutomaticallyGenerated()).thenReturn(automaticallyGenerated); + when(automaticallyGenerated.getIsNewServer()).thenReturn(false); // Default: not a new server when(settingsRepository.findSettings()).thenReturn(Optional.of(mockSettings)); when(userService.getTotalUsersCount()).thenReturn(80L); when(settingsRepository.save(any(UserLicenseSettings.class))) @@ -267,4 +270,222 @@ class UserLicenseSettingsServiceTest { verify(userService, times(1)).grandfatherAllOAuthUsers(); verify(userService, times(1)).grandfatherPendingSsoUsersWithoutSession(); } + + // ===== OAuth Eligibility Tests ===== + + @Test + void isOAuthEligible_grandfatheredUser_returnsTrue() { + // Grandfathered user should be eligible regardless of license + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("grandfathered-user"); + user.setOauthGrandfathered(true); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isOAuthEligible(user); + + assertEquals(true, result, "Grandfathered user should be eligible for OAuth"); + } + + @Test + void isOAuthEligible_nonGrandfatheredUserWithServerLicense_returnsTrue() { + // Non-grandfathered user with SERVER license should be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.SERVER); + + boolean result = service.isOAuthEligible(user); + + assertEquals(true, result, "Non-grandfathered user with SERVER license should be eligible"); + } + + @Test + void isOAuthEligible_nonGrandfatheredUserWithEnterpriseLicense_returnsTrue() { + // Non-grandfathered user with ENTERPRISE license should be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.ENTERPRISE); + + boolean result = service.isOAuthEligible(user); + + assertEquals( + true, result, "Non-grandfathered user with ENTERPRISE license should be eligible"); + } + + @Test + void isOAuthEligible_nonGrandfatheredUserWithNoLicense_returnsFalse() { + // Non-grandfathered user without license should NOT be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isOAuthEligible(user); + + assertEquals( + false, + result, + "Non-grandfathered user without paid license should NOT be eligible"); + } + + @Test + void isOAuthEligible_newUserWithServerLicense_returnsTrue() { + // New user (null) with SERVER license should be eligible for auto-creation + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.SERVER); + + boolean result = service.isOAuthEligible(null); + + assertEquals( + true, result, "New user with SERVER license should be eligible for auto-creation"); + } + + @Test + void isOAuthEligible_newUserWithNoLicense_returnsFalse() { + // New user (null) without license should NOT be eligible + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isOAuthEligible(null); + + assertEquals( + false, + result, + "New user without paid license should NOT be eligible for auto-creation"); + } + + @Test + void isOAuthEligible_licenseCheckerUnavailable_returnsFalse() { + // If LicenseKeyChecker is unavailable, OAuth should be blocked + when(licenseKeyCheckerProvider.getIfAvailable()).thenReturn(null); + + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + boolean result = service.isOAuthEligible(user); + + assertEquals( + false, result, "OAuth should be blocked when LicenseKeyChecker is unavailable"); + } + + // ===== SAML Eligibility Tests ===== + + @Test + void isSamlEligible_grandfatheredUser_returnsTrue() { + // Grandfathered user should be eligible for SAML regardless of license + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("grandfathered-user"); + user.setOauthGrandfathered(true); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isSamlEligible(user); + + assertEquals(true, result, "Grandfathered user should be eligible for SAML"); + } + + @Test + void isSamlEligible_nonGrandfatheredUserWithEnterpriseLicense_returnsTrue() { + // Non-grandfathered user with ENTERPRISE license should be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.ENTERPRISE); + + boolean result = service.isSamlEligible(user); + + assertEquals( + true, + result, + "Non-grandfathered user with ENTERPRISE license should be eligible for SAML"); + } + + @Test + void isSamlEligible_nonGrandfatheredUserWithServerLicense_returnsFalse() { + // Non-grandfathered user with SERVER license should NOT be eligible for SAML + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.SERVER); + + boolean result = service.isSamlEligible(user); + + assertEquals( + false, + result, + "Non-grandfathered user with SERVER license should NOT be eligible for SAML"); + } + + @Test + void isSamlEligible_nonGrandfatheredUserWithNoLicense_returnsFalse() { + // Non-grandfathered user without license should NOT be eligible + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.NORMAL); + + boolean result = service.isSamlEligible(user); + + assertEquals( + false, + result, + "Non-grandfathered user without ENTERPRISE license should NOT be eligible for SAML"); + } + + @Test + void isSamlEligible_newUserWithEnterpriseLicense_returnsTrue() { + // New user (null) with ENTERPRISE license should be eligible for auto-creation + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.ENTERPRISE); + + boolean result = service.isSamlEligible(null); + + assertEquals( + true, + result, + "New user with ENTERPRISE license should be eligible for SAML auto-creation"); + } + + @Test + void isSamlEligible_newUserWithServerLicense_returnsFalse() { + // New user (null) with SERVER license should NOT be eligible for SAML + when(licenseKeyChecker.getPremiumLicenseEnabledResult()).thenReturn(License.SERVER); + + boolean result = service.isSamlEligible(null); + + assertEquals( + false, + result, + "New user with SERVER license should NOT be eligible for SAML (requires ENTERPRISE)"); + } + + @Test + void isSamlEligible_licenseCheckerUnavailable_returnsFalse() { + // If LicenseKeyChecker is unavailable, SAML should be blocked + when(licenseKeyCheckerProvider.getIfAvailable()).thenReturn(null); + + stirling.software.proprietary.security.model.User user = + new stirling.software.proprietary.security.model.User(); + user.setUsername("test-user"); + user.setOauthGrandfathered(false); + + boolean result = service.isSamlEligible(user); + + assertEquals(false, result, "SAML should be blocked when LicenseKeyChecker is unavailable"); + } } diff --git a/build.gradle b/build.gradle index b85c2d413..17bcdb878 100644 --- a/build.gradle +++ b/build.gradle @@ -12,6 +12,8 @@ plugins { } import com.github.jk1.license.render.* +import groovy.json.JsonOutput +import groovy.json.JsonSlurper ext { springBootVersion = "3.5.6" @@ -57,7 +59,7 @@ repositories { allprojects { group = 'stirling.software' - version = '2.0.2' + version = '2.1.4' configurations.configureEach { exclude group: 'commons-logging', module: 'commons-logging' @@ -65,6 +67,51 @@ allprojects { } } +def writeIfChanged(File targetFile, String newContent) { + if (targetFile.getText('UTF-8') != newContent) { + targetFile.write(newContent, 'UTF-8') + } +} + +def updateTauriConfigVersion(String version) { + File tauriConfig = file('frontend/src-tauri/tauri.conf.json') + def parsed = new JsonSlurper().parse(tauriConfig) + parsed.version = version + + def formatted = JsonOutput.prettyPrint(JsonOutput.toJson(parsed)) + System.lineSeparator() + writeIfChanged(tauriConfig, formatted) +} + +def updateSimulationVersion(File fileToUpdate, String version) { + def content = fileToUpdate.getText('UTF-8') + def matcher = content =~ /(appVersion:\s*')([^']*)(')/ + + if (!matcher.find()) { + throw new GradleException("Could not locate appVersion in ${fileToUpdate} for synchronization") + } + + def updatedContent = matcher.replaceFirst("${matcher.group(1)}${version}${matcher.group(3)}") + writeIfChanged(fileToUpdate, updatedContent) +} + +tasks.register('syncAppVersion') { + group = 'versioning' + description = 'Synchronizes app version across desktop and simulation configs.' + + doLast { + def appVersion = project.version.toString() + println "Synchronizing application version to ${appVersion}" + updateTauriConfigVersion(appVersion) + + [ + 'frontend/src/core/testing/serverExperienceSimulations.ts', + 'frontend/src/proprietary/testing/serverExperienceSimulations.ts' + ].each { path -> + updateSimulationVersion(file(path), appVersion) + } + } +} + tasks.register('writeVersion', WriteProperties) { destinationFile = layout.projectDirectory.file('app/common/src/main/resources/version.properties') println "Writing version.properties to ${destinationFile.get().asFile.path}" @@ -314,7 +361,7 @@ tasks.named('bootRun') { tasks.named('build') { group = 'build' description = 'Delegates to :stirling-pdf:bootJar' - dependsOn ':stirling-pdf:bootJar', 'buildRestartHelper' + dependsOn ':stirling-pdf:bootJar', 'buildRestartHelper', 'syncAppVersion' doFirst { println "Delegating to :stirling-pdf:bootJar" diff --git a/devGuide/HowToAddNewLanguage.md b/devGuide/HowToAddNewLanguage.md index 6a9ed17f2..861772576 100644 --- a/devGuide/HowToAddNewLanguage.md +++ b/devGuide/HowToAddNewLanguage.md @@ -8,36 +8,33 @@ Fork Stirling-PDF and create a new branch out of `main`. -Then add a reference to the language in the navbar by adding a new language entry to the dropdown: +## Frontend Translation Files (TOML Format) -- Edit the file: [languages.html](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/app/core/src/main/resources/templates/fragments/languages.html) +### Add Language Directory and Translation File +1. Create a new language directory in `frontend/public/locales/` + - Use hyphenated format: `pl-PL` (not underscore) -For example, to add Polish, you would add: +2. Copy the reference translation file: + - Source: `frontend/public/locales/en-GB/translation.toml` + - Destination: `frontend/public/locales/pl-PL/translation.toml` -```html -

-``` +3. Translate all entries in the TOML file + - Keep the TOML structure intact + - Preserve all placeholders like `{n}`, `{total}`, `{filename}`, `{{variable}}` + - See `scripts/translations/README.md` for translation tools and workflows -The `data-bs-language-code` is the code used to reference the file in the next step. +4. Update the language selector in the frontend to include your new language -### Add Language Property File - -Start by copying the existing English property file: - -- [messages_en_GB.properties](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/app/core/src/main/resources/messages_en_GB.properties) - -Copy and rename it to `messages_{your data-bs-language-code here}.properties`. In the Polish example, you would set the name to `messages_pl_PL.properties`. - -Then simply translate all property entries within that file and make a Pull Request (PR) into `main` for others to use! - -If you do not have a Java IDE, I am happy to verify that the changes work once you raise the PR (but I won't be able to verify the translations themselves). +Then make a Pull Request (PR) into `main` for others to use! ## Handling Untranslatable Strings -Sometimes, certain strings in the properties file may not require translation because they are the same in the target language or are universal (like names of protocols, certain terminologies, etc.). To ensure accurate statistics for language progress, these strings should be added to the `ignore_translation.toml` file located in the `scripts` directory. This will exclude them from the translation progress calculations. +Sometimes, certain strings may not require translation because they are the same in the target language or are universal (like names of protocols, certain terminologies, etc.). To ensure accurate statistics for language progress, these strings should be added to the `ignore_translation.toml` file located in the `scripts` directory. This will exclude them from the translation progress calculations. -For example, if the English string `error=Error` does not need translation in Polish, add it to the `ignore_translation.toml` under the Polish section: +For example, if the English string `error` does not need translation in Polish, add it to the `ignore_translation.toml` under the Polish section: + +**Note**: Use underscores in `ignore_translation.toml` even though frontend uses hyphens (e.g., `pl_PL` not `pl-PL`) ```toml [pl_PL] @@ -50,27 +47,27 @@ ignore = [ ## Add New Translation Tags > [!IMPORTANT] -> If you add any new translation tags, they must first be added to the `messages_en_GB.properties` file. This ensures consistency across all language files. +> If you add any new translation tags, they must first be added to the `en-GB/translation.toml` file. This ensures consistency across all language files. -- New translation tags **must be added** to the `messages_en_GB.properties` file to maintain a reference for other languages. -- After adding the new tags to `messages_en_GB.properties`, add and translate them in the respective language file (e.g., `messages_pl_PL.properties`). +- New translation tags **must be added** to `frontend/public/locales/en-GB/translation.toml` to maintain a reference for other languages. +- After adding the new tags to `en-GB/translation.toml`, add and translate them in the respective language file (e.g., `pl-PL/translation.toml`). +- Use the scripts in `scripts/translations/` to validate and manage translations (see `scripts/translations/README.md`) Make sure to place the entry under the correct language section. This helps maintain the accuracy of translation progress statistics and ensures that the translation tool or scripts do not misinterpret the completion rate. -### Use this code to perform a local check +### Validation Commands -#### Windows command - -```powershell -python .github/scripts/check_language_properties.py --reference-file app\core\src\main\resources\messages_en_GB.properties --branch "" --files app\core\src\main\resources\messages_pl_PL.properties - -python .github/scripts/check_language_properties.py --reference-file app\core\src\main\resources\messages_en_GB.properties --branch "" --check-file app\core\src\main\resources\messages_pl_PL.properties -``` - -#### Linux command +Use the translation scripts in `scripts/translations/` directory: ```bash -python3 .github/scripts/check_language_properties.py --reference-file app/core/src/main/resources/messages_en_GB.properties --branch "" --files app/core/src/main/resources/messages_pl_PL.properties +# Analyze translation progress +python3 scripts/translations/translation_analyzer.py --language pl-PL -python3 .github/scripts/check_language_properties.py --reference-file app/core/src/main/resources/messages_en_GB.properties --branch "" --check-file app/core/src/main/resources/messages_pl_PL.properties +# Validate TOML structure +python3 scripts/translations/validate_json_structure.py --language pl-PL + +# Validate placeholders +python3 scripts/translations/validate_placeholders.py --language pl-PL ``` + +See `scripts/translations/README.md` for complete documentation. diff --git a/docker/Dockerfile.unified b/docker/Dockerfile.unified index 0ba7cfb3c..2968f569c 100644 --- a/docker/Dockerfile.unified +++ b/docker/Dockerfile.unified @@ -105,6 +105,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a gcompat \ libc6-compat \ libreoffice \ + imagemagick \ # pdftohtml poppler-utils \ # OCR MY PDF diff --git a/docker/backend/Dockerfile b/docker/backend/Dockerfile index f2421fa94..b946e1e61 100644 --- a/docker/backend/Dockerfile +++ b/docker/backend/Dockerfile @@ -81,6 +81,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a libc6-compat \ libreoffice \ ghostscript \ + imagemagick \ fontforge \ # pdftohtml poppler-utils \ diff --git a/docker/backend/Dockerfile.fat b/docker/backend/Dockerfile.fat index c54a162da..78d395d9d 100644 --- a/docker/backend/Dockerfile.fat +++ b/docker/backend/Dockerfile.fat @@ -74,6 +74,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a libc6-compat \ libreoffice \ ghostscript \ + imagemagick \ fontforge \ # pdftohtml poppler-utils \ diff --git a/docker/embedded/Dockerfile b/docker/embedded/Dockerfile index a38aee9b4..6b189f310 100644 --- a/docker/embedded/Dockerfile +++ b/docker/embedded/Dockerfile @@ -99,6 +99,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a libc6-compat \ libreoffice \ ghostscript \ + imagemagick \ fontforge \ # pdftohtml poppler-utils \ diff --git a/docker/embedded/Dockerfile.fat b/docker/embedded/Dockerfile.fat index 67e648aee..462daa901 100644 --- a/docker/embedded/Dockerfile.fat +++ b/docker/embedded/Dockerfile.fat @@ -101,6 +101,7 @@ RUN echo "@main https://dl-cdn.alpinelinux.org/alpine/edge/main" | tee -a /etc/a libc6-compat \ libreoffice \ ghostscript \ + imagemagick \ fontforge \ # pdftohtml poppler-utils \ diff --git a/docker/frontend/nginx.conf b/docker/frontend/nginx.conf index 3be5ec900..ef74321ef 100644 --- a/docker/frontend/nginx.conf +++ b/docker/frontend/nginx.conf @@ -103,8 +103,8 @@ http { add_header Cache-Control "public, immutable"; } - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + # Cache static assets (but not API endpoints) + location ~* ^(?!/api/).*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } diff --git a/docker/unified/nginx.conf b/docker/unified/nginx.conf index 1e47b8619..cbd5fee81 100644 --- a/docker/unified/nginx.conf +++ b/docker/unified/nginx.conf @@ -106,8 +106,8 @@ http { add_header Cache-Control "public, immutable"; } - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + # Cache static assets (but not API endpoints) + location ~* ^(?!/api/).*\.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 6e1f885e6..f788474bb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -105,6 +105,7 @@ "typescript": "^5.9.2", "typescript-eslint": "^8.44.1", "vite": "^7.1.7", + "vite-plugin-static-copy": "^3.1.4", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4" } @@ -456,6 +457,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -499,6 +501,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -579,6 +582,7 @@ "resolved": "https://registry.npmjs.org/@embedpdf/core/-/core-1.5.0.tgz", "integrity": "sha512-Yrh9XoVaT8cUgzgqpJ7hx5wg6BqQrCFirqqlSwVb+Ly9oNn4fZbR9GycIWmzJOU5XBnaOJjXfQSaDyoNP0woNA==", "license": "MIT", + "peer": true, "dependencies": { "@embedpdf/engines": "1.5.0", "@embedpdf/models": "1.5.0" @@ -678,6 +682,7 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-history/-/plugin-history-1.5.0.tgz", "integrity": "sha512-p7PTNNaIr4gH3jLwX+eLJe1DeUXgi21kVGN6SRx/pocH8esg4jqoOeD/YiRRZoZnPOiy0jBXVhkPkwSmY7a2hQ==", "license": "MIT", + "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -694,6 +699,7 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-interaction-manager/-/plugin-interaction-manager-1.5.0.tgz", "integrity": "sha512-ckHgTfvkW6c5Ta7Mc+Dl9C2foVnvEpqEJ84wyBnqrU0OWbe/jsiPhyKBVeartMGqNI/kVfaQTXupyrKhekAVmg==", "license": "MIT", + "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -711,6 +717,7 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-loader/-/plugin-loader-1.5.0.tgz", "integrity": "sha512-P4YpIZfaW69etYIjphyaL4cGl2pB14h3OdTE0tRQ2pZYZHFLTvlt4q9B3PVSdhlSrHK5nob7jfLGon2U7xCslg==", "license": "MIT", + "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -764,6 +771,7 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-render/-/plugin-render-1.5.0.tgz", "integrity": "sha512-ywwSj0ByrlkvrJIHKRzqxARkOZriki8VJUC+T4MV8fGyF4CzvCRJyKlPktahFz+VxhoodqTh7lBCib68dH+GvA==", "license": "MIT", + "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -798,6 +806,7 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-scroll/-/plugin-scroll-1.5.0.tgz", "integrity": "sha512-RNmTZCZ8X1mA8cw9M7TMDuhO9GtkOalGha2bBL3En3D1IlDRS7PzNNMSMV7eqT7OQICSTltlpJ8p8Qi5esvL/Q==", "license": "MIT", + "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -834,6 +843,7 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-selection/-/plugin-selection-1.5.0.tgz", "integrity": "sha512-zrxLBAZQoPswDuf9q9DrYaQc6B0Ysc2U1hueTjNH/4+ydfl0BFXZkKR63C2e3YmWtXvKjkoIj0GyPzsiBORLUw==", "license": "MIT", + "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -909,6 +919,7 @@ "resolved": "https://registry.npmjs.org/@embedpdf/plugin-viewport/-/plugin-viewport-1.5.0.tgz", "integrity": "sha512-G8GDyYRhfehw72+r4qKkydnA5+AU8qH67g01Y12b0DzI0VIzymh/05Z4dK8DsY3jyWPXJfw2hlg5+KDHaMBHgQ==", "license": "MIT", + "peer": true, "dependencies": { "@embedpdf/models": "1.5.0" }, @@ -1064,6 +1075,7 @@ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz", "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -1107,6 +1119,7 @@ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz", "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "@emotion/babel-plugin": "^11.13.5", @@ -2137,6 +2150,7 @@ "resolved": "https://registry.npmjs.org/@mantine/core/-/core-8.3.6.tgz", "integrity": "sha512-paTl+0x+O/QtgMtqVJaG8maD8sfiOdgPmLOyG485FmeGZ1L3KMdEkhxZtmdGlDFsLXhmMGQ57ducT90bvhXX5A==", "license": "MIT", + "peer": true, "dependencies": { "@floating-ui/react": "^0.27.16", "clsx": "^2.1.1", @@ -2187,6 +2201,7 @@ "resolved": "https://registry.npmjs.org/@mantine/hooks/-/hooks-8.3.6.tgz", "integrity": "sha512-liHfaWXHAkLjJy+Bkr29UsCwAoDQ/a64WrM67lksx8F0qqyjR5RQH8zVlhuOjdpQnwtlUkE/YiTvbJiPcoI0bw==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "^18.x || ^19.x" } @@ -2254,6 +2269,7 @@ "resolved": "https://registry.npmjs.org/@mui/material/-/material-7.3.5.tgz", "integrity": "sha512-8VVxFmp1GIm9PpmnQoCoYo0UWHoOrdA57tDL62vkpzEgvb/d71Wsbv4FRg7r1Gyx7PuSo0tflH34cdl/NvfHNQ==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4", "@mui/core-downloads-tracker": "^7.3.5", @@ -3186,6 +3202,7 @@ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-7.9.0.tgz", "integrity": "sha512-ggs5k+/0FUJcIgNY08aZTqpBTtbExkJMYMLSMwyucrhtWexVOEY1KJmhBsxf+E/Q15f5rbwBpj+t0t2AW2oCsQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=12.16" } @@ -3304,7 +3321,6 @@ "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.6.tgz", "integrity": "sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==", "license": "MIT", - "peer": true, "peerDependencies": { "acorn": "^8.9.0" } @@ -4081,6 +4097,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -4409,6 +4426,7 @@ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.2.tgz", "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -4419,6 +4437,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "dev": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -4488,6 +4507,7 @@ "integrity": "sha512-6m1I5RmHBGTnUGS113G04DMu3CpSdxCAU/UvtjNWL4Nuf3MW9tQhiJqRlHzChIkhy6kZSAQmc+I1bcGjE3yNKg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.3", "@typescript-eslint/types": "8.46.3", @@ -5201,7 +5221,6 @@ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.24.tgz", "integrity": "sha512-BM8kBhtlkkbnyl4q+HiF5R5BL0ycDPfihowulm02q3WYp2vxgPcJuZO866qa/0u3idbMntKEtVNuAUp5bw4teg==", "license": "MIT", - "peer": true, "dependencies": { "@vue/shared": "3.5.24" } @@ -5211,7 +5230,6 @@ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.24.tgz", "integrity": "sha512-RYP/byyKDgNIqfX/gNb2PB55dJmM97jc9wyF3jK7QUInYKypK2exmZMNwnjueWwGceEkP6NChd3D2ZVEp9undQ==", "license": "MIT", - "peer": true, "dependencies": { "@vue/reactivity": "3.5.24", "@vue/shared": "3.5.24" @@ -5222,7 +5240,6 @@ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.24.tgz", "integrity": "sha512-Z8ANhr/i0XIluonHVjbUkjvn+CyrxbXRIxR7wn7+X7xlcb7dJsfITZbkVOeJZdP8VZwfrWRsWdShH6pngMxRjw==", "license": "MIT", - "peer": true, "dependencies": { "@vue/reactivity": "3.5.24", "@vue/runtime-core": "3.5.24", @@ -5235,7 +5252,6 @@ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.24.tgz", "integrity": "sha512-Yh2j2Y4G/0/4z/xJ1Bad4mxaAk++C2v4kaa8oSYTMJBJ00/ndPuxCnWeot0/7/qafQFLh5pr6xeV6SdMcE/G1w==", "license": "MIT", - "peer": true, "dependencies": { "@vue/compiler-ssr": "3.5.24", "@vue/shared": "3.5.24" @@ -5262,6 +5278,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5669,7 +5686,6 @@ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">= 0.4" } @@ -5946,6 +5962,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -6993,7 +7010,8 @@ "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1521046.tgz", "integrity": "sha512-vhE6eymDQSKWUXwwA37NtTTVEzjtGVfDr3pRbsWEQ5onH/Snp2c+2xZHWJJawG/0hCCJLRGt4xVtEVUVILol4w==", "dev": true, - "license": "BSD-3-Clause" + "license": "BSD-3-Clause", + "peer": true }, "node_modules/dezalgo": { "version": "1.0.4", @@ -7388,6 +7406,7 @@ "integrity": "sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -7558,6 +7577,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -7724,8 +7744,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz", "integrity": "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/espree": { "version": "10.4.0", @@ -7790,7 +7809,6 @@ "resolved": "https://registry.npmjs.org/esrap/-/esrap-2.1.2.tgz", "integrity": "sha512-DgvlIQeowRNyvLPWW4PT7Gu13WznY288Du086E751mwwbsgr29ytBiYeLzAGIo0qk3Ujob0SDk8TiSaM5WQzNg==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" } @@ -8881,6 +8899,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.27.6" }, @@ -9357,7 +9376,6 @@ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz", "integrity": "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==", "license": "MIT", - "peer": true, "dependencies": { "@types/estree": "^1.0.6" } @@ -9678,6 +9696,7 @@ "integrity": "sha512-Pcfm3eZ+eO4JdZCXthW9tCDT3nF4K+9dmeZ+5X39n+Kqz0DDIABRP5CAEOHRFZk8RGuC2efksTJxrjp8EXCunQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@acemir/cssom": "^0.9.19", "@asamuzakjp/dom-selector": "^6.7.3", @@ -10264,8 +10283,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz", "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/locate-path": { "version": "6.0.0", @@ -11076,6 +11094,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-map": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-7.0.4.tgz", + "integrity": "sha512-tkAQEw8ysMzmkhgw8k+1U/iPhWNhykKnSk4Rd5zLoPJCuJaGRPo6YposrZgaxHKzDHdDWWZvE/Sk7hsL2X/CpQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pac-proxy-agent": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", @@ -11411,6 +11442,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -11690,6 +11722,7 @@ "resolved": "https://registry.npmjs.org/preact/-/preact-10.27.2.tgz", "integrity": "sha512-5SYSgFKSyhCbk6SrXyMpqjb5+MQBgfvEKE/OC+PujcY34sOpqtr+0AZQtPYx5IA6VxynQ7rUPCtKzyovpj9Bpg==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -12072,6 +12105,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12081,6 +12115,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13592,7 +13627,6 @@ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "license": "Apache-2.0", - "peer": true, "engines": { "node": ">= 0.4" } @@ -13801,6 +13835,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14102,6 +14137,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14183,6 +14219,7 @@ "dev": true, "hasInstallScript": true, "license": "MIT", + "peer": true, "dependencies": { "napi-postinstall": "^0.3.0" }, @@ -14387,6 +14424,7 @@ "integrity": "sha512-BxAKBWmIbrDgrokdGZH1IgkIk/5mMHDreLDmCJ0qpyJaAteP8NvMhkwr/ZCQNqNH97bw/dANTE9PDzqwJghfMQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -14479,6 +14517,25 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-static-copy": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-plugin-static-copy/-/vite-plugin-static-copy-3.1.4.tgz", + "integrity": "sha512-iCmr4GSw4eSnaB+G8zc2f4dxSuDjbkjwpuBLLGvQYR9IW7rnDzftnUjOH5p4RYR+d4GsiBqXRvzuFhs5bnzVyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.6.0", + "p-map": "^7.0.3", + "picocolors": "^1.1.1", + "tinyglobby": "^0.2.15" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, "node_modules/vite-tsconfig-paths": { "version": "5.1.4", "resolved": "https://registry.npmjs.org/vite-tsconfig-paths/-/vite-tsconfig-paths-5.1.4.tgz", @@ -14538,6 +14595,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -14551,6 +14609,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -15162,8 +15221,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz", "integrity": "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/zod": { "version": "3.25.76", diff --git a/frontend/package.json b/frontend/package.json index 5489f6b46..914b7bb1f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -152,6 +152,7 @@ "typescript": "^5.9.2", "typescript-eslint": "^8.44.1", "vite": "^7.1.7", + "vite-plugin-static-copy": "^3.1.4", "vite-tsconfig-paths": "^5.1.4", "vitest": "^3.2.4" }, diff --git a/frontend/public/Login/authentik.svg b/frontend/public/Login/authentik.svg new file mode 100644 index 000000000..26dc0189e --- /dev/null +++ b/frontend/public/Login/authentik.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/public/Login/cloudron.svg b/frontend/public/Login/cloudron.svg new file mode 100644 index 000000000..a4b50c421 --- /dev/null +++ b/frontend/public/Login/cloudron.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/public/Login/keycloak.svg b/frontend/public/Login/keycloak.svg new file mode 100644 index 000000000..30628c7b2 --- /dev/null +++ b/frontend/public/Login/keycloak.svg @@ -0,0 +1 @@ + diff --git a/frontend/public/Login/oidc.svg b/frontend/public/Login/oidc.svg new file mode 100644 index 000000000..440b54487 --- /dev/null +++ b/frontend/public/Login/oidc.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/public/locales/ar-AR/translation.toml b/frontend/public/locales/ar-AR/translation.toml index 0942b37b5..55a20edc2 100644 --- a/frontend/public/locales/ar-AR/translation.toml +++ b/frontend/public/locales/ar-AR/translation.toml @@ -163,6 +163,11 @@ unfavorite = "إزالة من المفضلة" fullscreen = "التبديل إلى وضع ملء الشاشة" sidebar = "التبديل إلى وضع الشريط الجانبي" +[backendStartup] +notFoundTitle = "لم يتم العثور على الخادم الخلفي" +retry = "إعادة المحاولة" +unreachable = "لا يمكن للتطبيق حالياً الاتصال بالخادم الخلفي. تحقق من حالة الخادم والاتصال بالشبكة، ثم حاول مرة أخرى." + [zipWarning] title = "ملف ZIP كبير" message = "هذا الملف ZIP يحتوي على {{count}} ملفات. هل تريد الاستخراج على أي حال؟" @@ -912,6 +917,9 @@ desc = "ابنِ تدفّقات عمل متعددة الخطوات بسلسلة desc = "تراكب ملف PDF فوق آخر" title = "تراكب ملفات PDF" +[home.pdfTextEditor] +title = "محرر نص PDF" +desc = "حرّر النصوص والصور الموجودة داخل ملفات PDF" [home.addText] tags = "نص,تعليق,تسمية" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "توقيع مرسوم" defaultImageLabel = "توقيع مرفوع" defaultTextLabel = "توقيع مكتوب" saveButton = "حفظ التوقيع" +savePersonal = "حفظ شخصي" +saveShared = "حفظ مشترك" saveUnavailable = "أنشئ توقيعاً أولاً لحفظه." noChanges = "التوقيع الحالي محفوظ بالفعل." +tempStorageTitle = "تخزين مؤقت في المتصفح" +tempStorageDescription = "يتم تخزين التواقيع في متصفحك فقط. ستُفقد إذا حذفت بيانات المتصفح أو بدّلت المتصفح." +personalHeading = "تواقيع شخصية" +sharedHeading = "تواقيع مشتركة" +personalDescription = "أنت فقط من يمكنه رؤية هذه التواقيع." +sharedDescription = "يمكن لجميع المستخدمين رؤية هذه التواقيع واستخدامها." [sign.saved.type] canvas = "رسم" @@ -3020,6 +3036,91 @@ title = "الحصول على معلومات عن PDF" header = "الحصول على معلومات عن PDF" submit = "الحصول على المعلومات" downloadJson = "تحميل JSON" +processing = "جارٍ استخراج المعلومات..." +results = "النتائج" +noResults = "شغّل الأداة لإنشاء تقرير." +downloads = "التنزيلات" +noneDetected = "لم يتم اكتشاف أي شيء" +indexTitle = "الفهرس" + +[getPdfInfo.report] +entryLabel = "ملخص المعلومات الكامل" +shortTitle = "معلومات PDF" + +[getPdfInfo.sections] +metadata = "البيانات الوصفية" +formFields = "حقول النماذج" +basicInfo = "معلومات أساسية" +documentInfo = "معلومات المستند" +compliance = "الامتثال" +encryption = "التشفير" +permissions = "الأذونات" +other = "أخرى" +perPageInfo = "معلومات لكل صفحة" +tableOfContents = "جدول المحتويات" + +[getPdfInfo.other] +attachments = "المرفقات" +embeddedFiles = "ملفات مضمنة" +javaScript = "JavaScript" +layers = "الطبقات" +structureTree = "شجرة البنية" +xmp = "بيانات XMP الوصفية" + +[getPdfInfo.perPage] +size = "الحجم" +annotations = "التعليقات التوضيحية" +images = "الصور" +links = "الروابط" +fonts = "الخطوط" +xobjects = "عدد كائنات XObject" +multimedia = "وسائط متعددة" + +[getPdfInfo.summary] +pages = "الصفحات" +fileSize = "حجم الملف" +pdfVersion = "إصدار PDF" +language = "اللغة" +title = "ملخص PDF" +author = "المؤلف" +created = "تم الإنشاء" +modified = "تم التعديل" +permsAll = "جميع الأذونات مسموح بها" +permsRestricted = "{{count}} قيود" +permsMixed = "بعض الأذونات مقيّدة" +hasCompliance = "يتضمن معايير امتثال" +noCompliance = "لا توجد معايير امتثال" +basic = "معلومات أساسية" +documentInfo = "معلومات المستند" +securityTitle = "حالة الأمان" +technical = "تقني" +overviewTitle = "نظرة عامة على PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF مشفّر - توجد حماية بكلمة مرور" +unencrypted = "PDF غير مُشفّر - لا توجد حماية بكلمة مرور" + +[getPdfInfo.summary.tech] +images = "الصور" +fonts = "الخطوط" +formFields = "حقول النماذج" +embeddedFiles = "ملفات مضمنة" +javaScript = "JavaScript" +layers = "الطبقات" +bookmarks = "الإشارات المرجعية" +multimedia = "وسائط متعددة" + +[getPdfInfo.summary.overview] +untitled = "مستند بلا عنوان" +unknown = "مؤلف غير معروف" +text = "هذا ملف PDF يحتوي على {{pages}} صفحة بعنوان {{title}} وتم إنشاؤه بواسطة {{author}} (إصدار PDF {{version}})." + +[getPdfInfo.error] +partial = "تعذّر معالجة بعض الملفات." +unexpected = "حدث خطأ غير متوقع أثناء الاستخراج." + +[getPdfInfo.status] +complete = "اكتمل الاستخراج" [extractPage] tags = "استخراج" @@ -3438,6 +3539,9 @@ signinTitle = "الرجاء تسجيل الدخول" ssoSignIn = "تسجيل الدخول عبر تسجيل الدخول الأحادي" oAuth2AutoCreateDisabled = "تم تعطيل الإنشاء التلقائي لمستخدم OAuth2" oAuth2AdminBlockedUser = "تم حظر تسجيل أو تسجيل دخول المستخدمين غير المسجلين حاليًا. يرجى الاتصال بالمسؤول." +oAuth2RequiresLicense = "يتطلب تسجيل الدخول عبر OAuth/SSO ترخيصاً مدفوعاً (Server أو Enterprise). يرجى الاتصال بالمسؤول لترقية باقتك." +saml2RequiresLicense = "يتطلب تسجيل الدخول عبر SAML ترخيصاً مدفوعاً (Server أو Enterprise). يرجى الاتصال بالمسؤول لترقية باقتك." +maxUsersReached = "تم الوصول إلى الحد الأقصى لعدد المستخدمين ضمن ترخيصك الحالي. يرجى الاتصال بالمسؤول لترقية باقتك أو إضافة مقاعد إضافية." oauth2RequestNotFound = "لم يتم العثور على طلب التفويض" oauth2InvalidUserInfoResponse = "استجابة معلومات المستخدم غير صالحة" oauth2invalidRequest = "طلب غير صالح" @@ -3771,7 +3875,7 @@ version = "الإصدار الحالي" title = "توثيق API" header = "توثيق API" desc = "عرض واختبار نقاط نهاية Stirling PDF API" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,توثيق,swagger,نقاط النهاية,تطوير" [cookieBanner.popUp] title = "كيف نستخدم ملفات تعريف الارتباط" @@ -3809,8 +3913,8 @@ title = "التحليلات" description = "تساعدنا هذه الملفات على فهم كيفية استخدام أدواتنا، كي نركّز على بناء الميزات الأكثر قيمة لمجتمعنا. كن مطمئنًا—‏Stirling PDF لا يمكنه ولن يتتبع محتوى المستندات التي تعمل عليها." [cookieBanner.services] -posthog = "PostHog Analytics" -scarf = "Scarf Pixel" +posthog = "تحليلات PostHog" +scarf = "Scarf بكسل" [removeMetadata] submit = "إزالة البيانات الوصفية" @@ -3846,14 +3950,17 @@ fitToWidth = "ملاءمة للعرض" actualSize = "الحجم الفعلي" [viewer] +cannotPreviewFile = "لا يمكن معاينة الملف" +dualPageView = "عرض صفحتين" firstPage = "الصفحة الأولى" lastPage = "الصفحة الأخيرة" -previousPage = "الصفحة السابقة" nextPage = "الصفحة التالية" +onlyPdfSupported = "عارض الملفات يدعم ملفات PDF فقط. يبدو أن هذا الملف بتنسيق مختلف." +previousPage = "الصفحة السابقة" +singlePageView = "عرض صفحة واحدة" +unknownFile = "ملف غير معروف" zoomIn = "تكبير" zoomOut = "تصغير" -singlePageView = "عرض صفحة واحدة" -dualPageView = "عرض صفحتين" [rightRail] closeSelected = "إغلاق الصفحات المحددة" @@ -3877,6 +3984,7 @@ toggleSidebar = "تبديل الشريط الجانبي" exportSelected = "تصدير الصفحات المحددة" toggleAnnotations = "تبديل ظهور التعليقات التوضيحية" annotationMode = "تبديل وضع التعليقات" +print = "طباعة PDF" draw = "رسم" save = "حفظ" saveChanges = "حفظ التغييرات" @@ -4494,6 +4602,7 @@ description = "عنوان URL أو اسم الملف الخاص بـ Impressum ( title = "الممتاز والمؤسسي" description = "تهيئة مفتاح الترخيص للمزايا الممتازة أو المؤسسية." license = "تهيئة الترخيص" +noInput = "يرجى تقديم مفتاح ترخيص أو ملف" [admin.settings.premium.licenseKey] toggle = "هل لديك مفتاح ترخيص أو ملف شهادة؟" @@ -4511,6 +4620,25 @@ line1 = "لا يمكن التراجع عن استبدال مفتاح الترخ line2 = "سيُفقد ترخيصك السابق نهائياً ما لم تكن قد احتفظت بنسخة احتياطية منه في مكان آخر." line3 = "مهم: احتفظ بمفاتيح الترخيص خاصة وآمنة. لا تشاركها علناً أبداً." +[admin.settings.premium.inputMethod] +text = "مفتاح الترخيص" +file = "ملف الشهادة" + +[admin.settings.premium.file] +label = "ملف شهادة الترخيص" +description = "قم بتحميل ملف الترخيص .lic أو .cert من عمليات الشراء دون اتصال" +choose = "اختر ملف الترخيص" +selected = "المحدد: {{filename}} ({{size}})" +successMessage = "تم تحميل ملف الترخيص وتفعيله بنجاح. لا يلزم إعادة التشغيل." + +[admin.settings.premium.currentLicense] +title = "الترخيص النشط" +file = "المصدر: ملف الترخيص ({{path}})" +key = "المصدر: مفتاح الترخيص" +type = "النوع: {{type}}" +noInput = "يرجى تقديم مفتاح ترخيص أو تحميل ملف شهادة" +success = "نجاح" + [admin.settings.premium.enabled] label = "تمكين الميزات الممتازة" description = "تمكين التحقق من مفتاح الترخيص لميزات Pro/المؤسسة" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} محدد" download = "تنزيل" delete = "حذف" unsupported = "غير مدعوم" +active = "نشط" addToUpload = "إضافة إلى الرفع" +closeFile = "إغلاق الملف" deleteAll = "حذف الكل" loadingFiles = "جارٍ تحميل الملفات..." noFiles = "لا توجد ملفات متاحة" @@ -5132,7 +5262,7 @@ upgrade = "الترقية الآن →" freeTitle = "ترخيص الخادم" overLimitTitle = "مطلوب ترخيص خادم" overLimitBody = "ترخيصنا يسمح حتى {{freeTierLimit}} مستخدمين مجاناً لكل خادم. لديك {{overLimitUserCopy}} مستخدمي Stirling. للمتابعة دون انقطاع، ارقَ إلى خطة خادم Stirling - مقاعد غير محدودة، تحرير نصوص PDF، وتحكم إداري كامل مقابل $99/خادم/شهرياً." -freeBody = "ترخيص Open-Core لدينا يسمح حتى {{freeTierLimit}} مستخدمين مجاناً لكل خادم. للتوسع بسلاسة والحصول على وصول مبكر إلى أداة تحرير نصوص PDF الجديدة، نوصي بخطة خادم Stirling - تحرير كامل ومقاعد غير محدودة مقابل $99/خادم/شهرياً." +freeBody = "يتيح ترخيصنا Open-Core ما يصل إلى {{freeTierLimit}} مستخدمًا مجانًا لكل خادم. للتوسع دون انقطاع، نوصي بخطة Stirling Server - مقاعد غير محدودة ودعم SSO مقابل $99/server/mo." [onboarding.desktopInstall] title = "تنزيل" @@ -5237,6 +5367,31 @@ error = "فشل تحديث حالة المستخدم" success = "تم حذف المستخدم بنجاح" error = "فشل حذف المستخدم" +[workspace.people.changePassword] +action = "تغيير كلمة المرور" +title = "تغيير كلمة المرور" +subtitle = "تحديث كلمة المرور لـ" +newPassword = "كلمة مرور جديدة" +confirmPassword = "تأكيد كلمة المرور" +placeholder = "أدخل كلمة مرور جديدة" +confirmPlaceholder = "أعد إدخال كلمة المرور الجديدة" +passwordRequired = "يرجى إدخال كلمة مرور جديدة" +passwordMismatch = "كلمتا المرور غير متطابقتين" +generateRandom = "إنشاء كلمة مرور آمنة" +generatedPreview = "كلمة المرور المُنشأة:" +copyTooltip = "نسخ إلى الحافظة" +copiedToClipboard = "تم نسخ كلمة المرور إلى الحافظة" +copyFailed = "فشل نسخ كلمة المرور" +sendEmail = "إرسال بريد إلكتروني للمستخدم حول هذا التغيير" +includePassword = "تضمين كلمة المرور الجديدة في البريد الإلكتروني" +forcePasswordChange = "إلزام المستخدم بتغيير كلمة المرور عند تسجيل الدخول التالي" +emailUnavailable = "بريد هذا المستخدم الإلكتروني غير صالح. تم تعطيل الإشعارات." +smtpDisabled = "تتطلب إشعارات البريد الإلكتروني تفعيل SMTP في الإعدادات." +notifyOnly = "سيتم إرسال بريد إلكتروني بدون كلمة المرور لإبلاغ المستخدم بأن المشرف قد غيّرها." +submit = "تحديث كلمة المرور" +success = "تم تحديث كلمة المرور بنجاح" +error = "فشل تحديث كلمة المرور" + [workspace.people.emailInvite] tab = "دعوة عبر البريد الإلكتروني" description = "اكتب أو الصق عناوين البريد الإلكتروني أدناه مفصولة بفواصل. سيتلقى المستخدمون بيانات اعتماد تسجيل الدخول عبر البريد الإلكتروني." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "مطلوب عنوان بريد إلكتروني واحد على الأقل" submit = "إرسال الدعوات" success = "تمت دعوة المستخدم/المستخدمين بنجاح" -partialSuccess = "فشلت بعض الدعوات" +partialFailure = "فشل بعض الدعوات" allFailed = "فشلت دعوة المستخدمين" error = "فشل إرسال الدعوات" @@ -5770,6 +5925,7 @@ subtitle = "سجّل الدخول بحساب Stirling الخاص بك" [setup.selfhosted] title = "سجّل الدخول إلى الخادم" subtitle = "أدخل بيانات اعتماد الخادم" +link = "أو الاتصال بحساب مُستضاف ذاتيًا" [setup.server] title = "الاتصال بالخادم" @@ -5788,6 +5944,14 @@ description = "أدخل عنوان URL الكامل لخادم Stirling PDF ال emptyUrl = "يرجى إدخال عنوان URL للخادم" unreachable = "تعذّر الاتصال بالخادم" testFailed = "فشل اختبار الاتصال" +configFetch = "فشل في جلب إعدادات الخادم. يرجى التحقق من عنوان URL والمحاولة مرة أخرى." + +[setup.server.error.securityDisabled] +title = "تسجيل الدخول غير مفعّل" +body = "لا يحتوي هذا الخادم على تسجيل دخول مفعّل. للاتصال بهذا الخادم، يجب تمكين المصادقة:" +step1 = "عيّن DOCKER_ENABLE_SECURITY=true في بيئتك" +step2 = "أو عيّن security.enableLogin=true في settings.yml" +step3 = "أعد تشغيل الخادم" [setup.login] title = "تسجيل الدخول" @@ -5797,6 +5961,13 @@ submit = "تسجيل الدخول" signInWith = "تسجيل الدخول باستخدام" oauthPending = "جارٍ فتح المتصفح للمصادقة..." orContinueWith = "أو المتابعة بالبريد الإلكتروني" +serverRequirement = "ملاحظة: يجب أن يكون تسجيل الدخول مفعّلاً على الخادم." +showInstructions = "كيفية التمكين؟" +hideInstructions = "إخفاء الإرشادات" +instructions = "لتمكين تسجيل الدخول على خادم Stirling PDF الخاص بك:" +instructionsEnvVar = "عيّن متغيّر البيئة:" +instructionsOrYml = "أو في settings.yml:" +instructionsRestart = "ثم أعد تشغيل الخادم لتصبح التغييرات نافذة." [setup.login.username] label = "اسم المستخدم" @@ -5853,6 +6024,7 @@ earlyAccess = "وصول مبكر" reset = "إعادة تعيين التغييرات" downloadJson = "تنزيل JSON" generatePdf = "توليد PDF" +saveChanges = "حفظ التغييرات" [pdfTextEditor.options.autoScaleText] title = "ضبط النص تلقائياً ليتناسب مع الصناديق" @@ -5890,6 +6062,8 @@ alpha = "هذا العارض بنسخة ألفا ولا يزال يتطور—ق [pdfTextEditor.empty] title = "لم يتم تحميل مستند" subtitle = "حمّل ملف PDF أو JSON لبدء تحرير محتوى النص." +dropzone = "اسحب وأفلت ملف PDF أو JSON هنا، أو انقر للاستعراض" +dropzoneWithFiles = "حدد ملفًا من علامة تبويب الملفات، أو اسحب وأفلت ملف PDF أو JSON هنا، أو انقر للاستعراض" [pdfTextEditor.welcomeBanner] title = "مرحباً بك في محرر نصوص PDF (وصول مبكر)" diff --git a/frontend/public/locales/az-AZ/translation.toml b/frontend/public/locales/az-AZ/translation.toml index d4c20aea6..5512e9e55 100644 --- a/frontend/public/locales/az-AZ/translation.toml +++ b/frontend/public/locales/az-AZ/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Seçilmişlərdən çıxar" fullscreen = "Tam ekran rejiminə keç" sidebar = "Yan panel rejiminə keç" +[backendStartup] +notFoundTitle = "Backend tapılmadı" +retry = "Yenidən cəhd et" +unreachable = "Tətbiq hazırda backend-ə qoşula bilmir. Backend-in vəziyyətini və şəbəkə bağlantısını yoxlayın, sonra yenidən cəhd edin." + [zipWarning] title = "Böyük ZIP faylı" message = "Bu ZIP {{count}} fayl ehtiva edir. Yenə də çıxarılsın?" @@ -912,6 +917,9 @@ desc = "PDF əməliyyatlarını zəncirləyərək çoxaddımlı iş axınları q desc = "Bir PDF-i digərinin üstünə qoyur" title = "Üst-Üstə Qoy" +[home.pdfTextEditor] +title = "PDF Mətn Redaktoru" +desc = "PDF-lərin içindəki mövcud mətn və şəkilləri redaktə edin" [home.addText] tags = "mətn,şərh,etiket" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Çəkilmiş imza" defaultImageLabel = "Yüklənmiş imza" defaultTextLabel = "Yazılmış imza" saveButton = "İmzanı saxla" +savePersonal = "Şəxsi yadda saxla" +saveShared = "Paylaşılanı yadda saxla" saveUnavailable = "Saxlamaq üçün əvvəlcə imza yaradın." noChanges = "Cari imza artıq saxlanıb." +tempStorageTitle = "Müvəqqəti brauzer yaddaşı" +tempStorageDescription = "İmzalar yalnız brauzerinizdə saxlanılır. Brauzer məlumatlarını təmizləsəniz və ya brauzer dəyişsəniz, itəcək." +personalHeading = "Şəxsi imzalar" +sharedHeading = "Paylaşılan imzalar" +personalDescription = "Bu imzaları yalnız siz görə bilirsiniz." +sharedDescription = "Bütün istifadəçilər bu imzaları görə və istifadə edə bilərlər." [sign.saved.type] canvas = "Rəsm" @@ -3020,6 +3036,91 @@ title = "PDF Barəsində Məlumat Əldə Et" header = "PDF Barəsində Məlumat Əldə Et" submit = "Məlumat Əldə Et" downloadJson = "JSON yüklə" +processing = "Məlumat çıxarılır..." +results = "Nəticələr" +noResults = "Hesabat yaratmaq üçün aləti işə salın." +downloads = "Yükləmələr" +noneDetected = "Heç nə aşkar edilmədi" +indexTitle = "İndeks" + +[getPdfInfo.report] +entryLabel = "Tam məlumat xülasəsi" +shortTitle = "PDF Məlumatı" + +[getPdfInfo.sections] +metadata = "Metaməlumat" +formFields = "Forma sahələri" +basicInfo = "Əsas Məlumat" +documentInfo = "Sənəd Məlumatı" +compliance = "Uyğunluq" +encryption = "Şifrələmə" +permissions = "İcazələr" +other = "Digər" +perPageInfo = "Hər səhifə üzrə məlumat" +tableOfContents = "Mündəricat" + +[getPdfInfo.other] +attachments = "Əlavələr" +embeddedFiles = "Gömülü fayllar" +javaScript = "JavaScript" +layers = "Qatlar" +structureTree = "Struktur ağacı" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Ölçü" +annotations = "Annotasiyalar" +images = "Şəkillər" +links = "Keçidlər" +fonts = "Şriftlər" +xobjects = "XObject sayları" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Səhifələr" +fileSize = "Fayl Ölçüsü" +pdfVersion = "PDF Versiyası" +language = "Dil" +title = "PDF Xülasəsi" +author = "Müəllif" +created = "Yaradılıb" +modified = "Dəyişdirilib" +permsAll = "Bütün icazələr verilib" +permsRestricted = "{{count}} məhdudiyyət" +permsMixed = "Bəzi icazələr məhdudlaşdırılıb" +hasCompliance = "Uyğunluq standartları mövcuddur" +noCompliance = "Uyğunluq standartları yoxdur" +basic = "Əsas Məlumat" +documentInfo = "Sənəd Məlumatı" +securityTitle = "Təhlükəsizlik Vəziyyəti" +technical = "Texniki" +overviewTitle = "PDF İcmalı" + +[getPdfInfo.summary.security] +encrypted = "Şifrələnmiş PDF - Parol ilə qorunur" +unencrypted = "Şifrələnməmiş PDF - Parol qorunması yoxdur" + +[getPdfInfo.summary.tech] +images = "Şəkillər" +fonts = "Şriftlər" +formFields = "Forma sahələri" +embeddedFiles = "Gömülü fayllar" +javaScript = "JavaScript" +layers = "Qatlar" +bookmarks = "Əlfəcinlər" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "adsız sənəd" +unknown = "Naməlum müəllif" +text = "Bu, {{author}} tərəfindən yaradılmış, {{title}} adlı, {{pages}} səhifəlik PDF-dir (PDF versiyası {{version}})." + +[getPdfInfo.error] +partial = "Bəzi faylları emal etmək mümkün olmadı." +unexpected = "Çıxarılma zamanı gözlənilməz xəta baş verdi." + +[getPdfInfo.status] +complete = "Çıxarılma tamamlandı" [extractPage] tags = "çıxar" @@ -3438,6 +3539,9 @@ signinTitle = "Zəhmət olmasa, daxil olun" ssoSignIn = "Single Sign-on vasitəsilə daxil olun" oAuth2AutoCreateDisabled = "OAUTH2 Auto-Create İstifadəçisi Deaktivləşdirilmişdir" oAuth2AdminBlockedUser = "Qeydiyyatdan keçməmiş istifadəçilərin qeydiyyatı və daxil olması hal-hazırda bloklanmışdır. Zəhmət olmasa, administratorla əlaqə saxlayın." +oAuth2RequiresLicense = "OAuth/SSO ilə giriş üçün ödənişli lisenziya (Server və ya Enterprise) tələb olunur. Planınızı yüksəltmək üçün administratorla əlaqə saxlayın." +saml2RequiresLicense = "SAML ilə giriş üçün ödənişli lisenziya (Server və ya Enterprise) tələb olunur. Planınızı yüksəltmək üçün administratorla əlaqə saxlayın." +maxUsersReached = "Mövcud lisenziyanız üçün maksimum istifadəçi sayına çatılıb. Planınızı yüksəltmək və ya əlavə yerlər əlavə etmək üçün administratorla əlaqə saxlayın." oauth2RequestNotFound = "Təsdiqlənmə sorğusu tapılmadı" oauth2InvalidUserInfoResponse = "Yanlış İstifadəçi Məlumatı Cavabı" oauth2invalidRequest = "Etibarsız Sorğu" @@ -3846,14 +3950,17 @@ fitToWidth = "Eninə sığdır" actualSize = "Həqiqi ölçü" [viewer] +cannotPreviewFile = "Faylın önizlənməsi mümkün deyil" +dualPageView = "İki Səhifə Görünüşü" firstPage = "Birinci səhifə" lastPage = "Son səhifə" -previousPage = "Əvvəlki səhifə" nextPage = "Növbəti səhifə" +onlyPdfSupported = "Görüntüləyici yalnız PDF fayllarını dəstəkləyir. Bu fayl fərqli formatda görünür." +previousPage = "Əvvəlki səhifə" +singlePageView = "Tək Səhifə Görünüşü" +unknownFile = "Naməlum fayl" zoomIn = "Böyüt" zoomOut = "Kiçilt" -singlePageView = "Tək Səhifə Görünüşü" -dualPageView = "İki Səhifə Görünüşü" [rightRail] closeSelected = "Seçilmiş faylları bağla" @@ -3877,6 +3984,7 @@ toggleSidebar = "Yan paneli aç/bağla" exportSelected = "Seçilmiş səhifələri ixrac et" toggleAnnotations = "Annotasiyaların görünməsini dəyiş" annotationMode = "Annotasiya rejimini dəyiş" +print = "PDF-i çap et" draw = "Rəsm çək" save = "Yadda saxla" saveChanges = "Dəyişiklikləri yadda saxla" @@ -4407,7 +4515,7 @@ description = "Daha geniş sistem müvəqqəti qovluğunu təmizləyib-təmizlə label = "Proses İcraedicisi Limitləri" description = "Hər icraedici üçün sessiya limitlərini və taym-outları konfiqurasiya edin" libreOffice = "LibreOffice" -pdfToHtml = "PDF to HTML" +pdfToHtml = "PDF-dən HTML-ə" qpdf = "QPDF" tesseract = "Tesseract OCR" pythonOpenCv = "Python OpenCV" @@ -4494,6 +4602,7 @@ description = "Impressum üçün URL və ya fayl adı (bəzi yurisdiksiyalarda t title = "Premium və Enterprise" description = "Premium və ya enterprise lisenziya açarınızı konfiqurasiya edin." license = "Lisenziya Konfiqurasiyası" +noInput = "Zəhmət olmasa lisenziya açarı və ya fayl təqdim edin" [admin.settings.premium.licenseKey] toggle = "Lisenziya açarınız və ya sertifikat faylınız var?" @@ -4511,6 +4620,25 @@ line1 = "Cari lisenziya açarının üzərinə yazmaq geri alına bilməz." line2 = "Ehtiyat nüsxəsi yoxdursa, əvvəlki lisenziyanız birdəfəlik itəcək." line3 = "Vacibdir: Lisenziya açarlarını məxfi və təhlükəsiz saxlayın. Heç vaxt onları ictimai paylaşmayın." +[admin.settings.premium.inputMethod] +text = "Lisenziya açarı" +file = "Sertifikat faylı" + +[admin.settings.premium.file] +label = "Lisenziya sertifikat faylı" +description = "Oflayn alışdan əldə etdiyiniz .lic və ya .cert lisenziya faylını yükləyin" +choose = "Lisenziya faylını seçin" +selected = "Seçildi: {{filename}} ({{size}})" +successMessage = "Lisenziya faylı uğurla yüklənib və aktivləşdirilib. Yenidən başlatmağa ehtiyac yoxdur." + +[admin.settings.premium.currentLicense] +title = "Aktiv lisenziya" +file = "Mənbə: Lisenziya faylı ({{path}})" +key = "Mənbə: Lisenziya açarı" +type = "Növ: {{type}}" +noInput = "Zəhmət olmasa lisenziya açarı verin və ya sertifikat faylı yükləyin" +success = "Uğurlu" + [admin.settings.premium.enabled] label = "Premium Xüsusiyyətlərini aktiv et" description = "Pro/enterprise xüsusiyyətləri üçün lisenziya açarı yoxlamalarını aktiv et" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} seçildi" download = "Endir" delete = "Sil" unsupported = "Dəstəklənmir" +active = "Aktiv" addToUpload = "Yükləməyə əlavə et" +closeFile = "Faylı bağla" deleteAll = "Hamısını sil" loadingFiles = "Fayllar yüklənir..." noFiles = "Fayl mövcud deyil" @@ -5132,7 +5262,7 @@ upgrade = "İndi yüksəlt →" freeTitle = "Server lisenziyası" overLimitTitle = "Server lisenziyası tələb olunur" overLimitBody = "Lisenziyalaşmamız hər server üçün pulsuz olaraq maksimum {{freeTierLimit}} istifadəçiyə icazə verir. Sizdə {{overLimitUserCopy}} Stirling istifadəçisi var. Fasiləsiz davam etmək üçün Stirling Server planına yüksəldin - limitsiz yerlər, PDF mətn redaktəsi və tam admin nəzarəti cəmi $99/server/ay." -freeBody = "Bizim Open-Core lisenziyası hər server üçün pulsuz olaraq maksimum {{freeTierLimit}} istifadəçiyə icazə verir. Fasiləsiz miqyaslanmaq və yeni PDF mətn redaktəsi alətimizə erkən çıxış əldə etmək üçün Stirling Server planını tövsiyə edirik — tam redaktə və limitsiz yerlər $99/server/ay." +freeBody = "Bizim Open-Core lisenziyalaşdırmamız hər server üçün pulsuz olaraq ən çox {{freeTierLimit}} istifadəçiyə icazə verir. Fasiləsiz miqyaslama üçün Stirling Server planını tövsiyə edirik - limitsiz yerlərSSO dəstəyi $99/server/ay." [onboarding.desktopInstall] title = "Yüklə" @@ -5237,6 +5367,31 @@ error = "İstifadəçi statusunu yeniləmək alınmadı" success = "İstifadəçi uğurla silindi" error = "İstifadəçini silmək alınmadı" +[workspace.people.changePassword] +action = "Parolu dəyiş" +title = "Parolu dəyiş" +subtitle = "Aşağıdakı istifadəçi üçün parolu yeniləyin" +newPassword = "Yeni parol" +confirmPassword = "Parolu təsdiq edin" +placeholder = "Yeni parolu daxil edin" +confirmPlaceholder = "Yeni parolu yenidən daxil edin" +passwordRequired = "Zəhmət olmasa yeni parolu daxil edin" +passwordMismatch = "Parollar uyğun gəlmir" +generateRandom = "Təhlükəsiz parol yaradın" +generatedPreview = "Yaradılmış parol:" +copyTooltip = "Buferə kopyala" +copiedToClipboard = "Parol buferə kopyalandı" +copyFailed = "Parolu kopyalamaq alınmadı" +sendEmail = "Bu dəyişiklik barədə istifadəçiyə e-poçt göndərin" +includePassword = "E-poçta yeni parolu daxil edin" +forcePasswordChange = "Növbəti girişdə istifadəçini parolu dəyişməyə məcbur et" +emailUnavailable = "Bu istifadəçinin e-poçtu etibarlı e-poçt ünvanı deyil. Bildirişlər söndürülüb." +smtpDisabled = "E-poçt bildirişləri üçün parametrlərdə SMTP aktiv olmalıdır." +notifyOnly = "Parol olmadan e-poçt göndəriləcək; istifadəçiyə adminin onu dəyişdiyi bildiriləcək." +submit = "Parolu yenilə" +success = "Parol uğurla yeniləndi" +error = "Parolu yeniləmək alınmadı" + [workspace.people.emailInvite] tab = "E-poçt Dəvəti" description = "Aşağıya vergüllə ayrılmış e-poçtları yazın və ya yapışdırın. İstifadəçilərə giriş məlumatları e-poçtla göndəriləcək." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Ən azı bir e-poçt ünvanı tələb olunur" submit = "Dəvətnamələri göndər" success = "istifadəçi(lər) uğurla dəvət olundu" -partialSuccess = "Bəzi dəvətnamələr alınmadı" +partialFailure = "Bəzi dəvətlər uğursuz oldu" allFailed = "İstifadəçiləri dəvət etmək alınmadı" error = "Dəvətnamələri göndərmək alınmadı" @@ -5288,8 +5443,8 @@ emailDisabled = "E-poçt dəvətləri üçün ayarlarda SMTP konfiqurasiyası v [workspace.people.license] users = "istifadəçi" availableSlots = "Mövcud yerlər" -grandfathered = "Grandfathered" -grandfatheredShort = "{{count}} grandfathered" +grandfathered = "Əvvəlki şərtlərlə" +grandfatheredShort = "{{count}} əvvəlki şərtlərlə" fromLicense = "lisenziyadan" slotsAvailable = "{{count}} istifadəçi yeri mövcuddur" noSlotsAvailable = "Mövcud yer yoxdur" @@ -5770,6 +5925,7 @@ subtitle = "Stirling hesabınızla daxil olun" [setup.selfhosted] title = "Serverə daxil olun" subtitle = "Server məlumatlarınızı daxil edin" +link = "və ya self-hosted hesaba qoşulun" [setup.server] title = "Serverə qoşulun" @@ -5788,6 +5944,14 @@ description = "Öz Stirling PDF serverinizin tam URL ünvanını daxil edin" emptyUrl = "Zəhmət olmasa server URL-i daxil edin" unreachable = "Serverə qoşulmaq mümkün olmadı" testFailed = "Bağlantı testi uğursuz oldu" +configFetch = "Server konfiqurasiyasını əldə etmək mümkün olmadı. URL-i yoxlayın və yenidən cəhd edin." + +[setup.server.error.securityDisabled] +title = "Giriş aktiv deyil" +body = "Bu serverdə giriş aktiv deyil. Bu serverə qoşulmaq üçün autentifikasiya aktiv edilməlidir:" +step1 = "Mühitinizdə DOCKER_ENABLE_SECURITY=true təyin edin" +step2 = "Yaxud settings.yml faylında security.enableLogin=true təyin edin" +step3 = "Serveri yenidən başladın" [setup.login] title = "Daxil ol" @@ -5797,6 +5961,13 @@ submit = "Daxil ol" signInWith = "Bununla daxil ol" oauthPending = "Təsdiqləmə üçün brauzer açılır..." orContinueWith = "Və ya e-poçt ilə davam edin" +serverRequirement = "Qeyd: Serverdə giriş funksiyası aktiv olmalıdır." +showInstructions = "Necə aktivləşdirmək olar?" +hideInstructions = "Təlimatları gizlət" +instructions = "Stirling PDF serverinizdə girişi aktivləşdirmək üçün:" +instructionsEnvVar = "Mühit dəyişənini təyin edin:" +instructionsOrYml = "Və ya settings.yml faylında:" +instructionsRestart = "Dəyişikliklərin qüvvəyə minməsi üçün serveri yenidən başladın." [setup.login.username] label = "İstifadəçi adı" @@ -5853,6 +6024,7 @@ earlyAccess = "Erkən Giriş" reset = "Dəyişiklikləri sıfırla" downloadJson = "JSON-u endir" generatePdf = "PDF yarat" +saveChanges = "Dəyişiklikləri yadda saxla" [pdfTextEditor.options.autoScaleText] title = "Mətni avtomatik miqyasla" @@ -5890,6 +6062,8 @@ alpha = "Bu alfa görüntüləyici hələ inkişaf edir—bəzi şriftlər, rən [pdfTextEditor.empty] title = "Sənəd yüklənməyib" subtitle = "Mətn məzmununu redaktə etməyə başlamaq üçün PDF və ya JSON faylı yükləyin." +dropzone = "Buraya PDF və ya JSON faylını sürükləyib buraxın və ya baxmaq üçün klikləyin" +dropzoneWithFiles = "Fayllar vərəqindən bir fayl seçin və ya buraya PDF və ya JSON faylını sürükləyib buraxın, yaxud baxmaq üçün klikləyin" [pdfTextEditor.welcomeBanner] title = "PDF Text Editor-ə xoş gəldiniz (Erkən Giriş)" diff --git a/frontend/public/locales/bg-BG/translation.toml b/frontend/public/locales/bg-BG/translation.toml index 2d33576bf..b25a7f246 100644 --- a/frontend/public/locales/bg-BG/translation.toml +++ b/frontend/public/locales/bg-BG/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Премахване от любими" fullscreen = "Превключване към режим на цял екран" sidebar = "Превключване към режим със странична лента" +[backendStartup] +notFoundTitle = "Бекендът не е намерен" +retry = "Опитай отново" +unreachable = "Приложението в момента не може да се свърже с бекенда. Проверете състоянието на бекенда и мрежовата свързаност, след което опитайте отново." + [zipWarning] title = "Голям ZIP файл" message = "Този ZIP съдържа {{count}} файла. Да се извлече въпреки това?" @@ -912,6 +917,9 @@ desc = "Създавайте многостъпкови работни проц desc = "Наслагва PDF файлове върху друг PDF" title = "Наслагване PDF-и" +[home.pdfTextEditor] +title = "Редактор на текст в PDF" +desc = "Редактирайте съществуващ текст и изображения в PDF файлове" [home.addText] tags = "текст,анотация,етикет" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Нарисуван подпис" defaultImageLabel = "Качен подпис" defaultTextLabel = "Въведен подпис" saveButton = "Запази подписа" +savePersonal = "Запази като личен" +saveShared = "Запази като споделен" saveUnavailable = "Първо създайте подпис, за да го запазите." noChanges = "Текущият подпис вече е запазен." +tempStorageTitle = "Временно съхранение в браузъра" +tempStorageDescription = "Подписите се съхраняват само във вашия браузър. Ще бъдат загубени, ако изчистите данните на браузъра или смените браузър." +personalHeading = "Лични подписи" +sharedHeading = "Споделени подписи" +personalDescription = "Само вие можете да виждате тези подписи." +sharedDescription = "Всички потребители могат да виждат и използват тези подписи." [sign.saved.type] canvas = "Рисунка" @@ -3020,6 +3036,91 @@ title = "Вземете информация за PDF" header = "Вземете информация за PDF" submit = "Вземете информация" downloadJson = "Изтеглете JSON" +processing = "Извличане на информация..." +results = "Резултати" +noResults = "Стартирайте инструмента, за да генерирате отчет." +downloads = "Изтегляния" +noneDetected = "Нищо не е открито" +indexTitle = "Индекс" + +[getPdfInfo.report] +entryLabel = "Пълно резюме на информацията" +shortTitle = "Информация за PDF" + +[getPdfInfo.sections] +metadata = "Метаданни" +formFields = "Полета на формуляра" +basicInfo = "Основна информация" +documentInfo = "Информация за документа" +compliance = "Съответствие" +encryption = "Шифриране" +permissions = "Разрешения" +other = "Друго" +perPageInfo = "Информация по страници" +tableOfContents = "Съдържание" + +[getPdfInfo.other] +attachments = "Прикачени файлове" +embeddedFiles = "Вградени файлове" +javaScript = "JavaScript" +layers = "Слоеве" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Размер" +annotations = "Анотации" +images = "Изображения" +links = "Връзки" +fonts = "Шрифтове" +xobjects = "Брой XObject" +multimedia = "Мултимедия" + +[getPdfInfo.summary] +pages = "Страници" +fileSize = "Размер на файла" +pdfVersion = "Версия на PDF" +language = "Език" +title = "Обобщение на PDF" +author = "Автор" +created = "Създаден" +modified = "Променен" +permsAll = "Всички разрешения са позволени" +permsRestricted = "{{count}} ограничения" +permsMixed = "Някои разрешения са ограничени" +hasCompliance = "Има стандарти за съответствие" +noCompliance = "Няма стандарти за съответствие" +basic = "Основна информация" +documentInfo = "Информация за документа" +securityTitle = "Състояние на сигурността" +technical = "Технически" +overviewTitle = "Преглед на PDF" + +[getPdfInfo.summary.security] +encrypted = "Шифриран PDF - налична защита с парола" +unencrypted = "Нешифриран PDF - няма защита с парола" + +[getPdfInfo.summary.tech] +images = "Изображения" +fonts = "Шрифтове" +formFields = "Полета на формуляра" +embeddedFiles = "Вградени файлове" +javaScript = "JavaScript" +layers = "Слоеве" +bookmarks = "Отметки" +multimedia = "Мултимедия" + +[getPdfInfo.summary.overview] +untitled = "неозаглавен документ" +unknown = "Неизвестен автор" +text = "Това е {{pages}}-страничен PDF със заглавие {{title}}, създаден от {{author}} (версия на PDF {{version}})." + +[getPdfInfo.error] +partial = "Някои файлове не можаха да бъдат обработени." +unexpected = "Неочаквана грешка по време на извличане." + +[getPdfInfo.status] +complete = "Извличането е завършено" [extractPage] tags = "извличане" @@ -3438,6 +3539,9 @@ signinTitle = "Моля впишете се" ssoSignIn = "Влизане чрез еднократно влизане" oAuth2AutoCreateDisabled = "OAUTH2 Автоматично създаване на потребител е деактивирано" oAuth2AdminBlockedUser = "Регистрацията или влизането на нерегистрирани потребители в момента е блокирано. Моля, свържете се с администратора." +oAuth2RequiresLicense = "Вход с OAuth/SSO изисква платен лиценз (Server или Enterprise). Моля, свържете се с администратора, за да надстроите плана си." +saml2RequiresLicense = "Вход със SAML изисква платен лиценз (Server или Enterprise). Моля, свържете се с администратора, за да надстроите плана си." +maxUsersReached = "Достигнат е максималният брой потребители за текущия ви лиценз. Моля, свържете се с администратора, за да надстроите плана си или да добавите още места." oauth2RequestNotFound = "Заявката за оторизация не е намерена" oauth2InvalidUserInfoResponse = "Невалидна информация за потребителя" oauth2invalidRequest = "Невалидна заявка" @@ -3846,14 +3950,17 @@ fitToWidth = "Побиране по ширина" actualSize = "Действителен размер" [viewer] +cannotPreviewFile = "Не може да се визуализира файлът" +dualPageView = "Изглед: две страници" firstPage = "Първа страница" lastPage = "Последна страница" -previousPage = "Предишна страница" nextPage = "Следваща страница" +onlyPdfSupported = "Прегледачът поддържа само PDF файлове. Този файл изглежда е в друг формат." +previousPage = "Предишна страница" +singlePageView = "Изглед: една страница" +unknownFile = "Непознат файл" zoomIn = "Увеличи" zoomOut = "Намали" -singlePageView = "Изглед: една страница" -dualPageView = "Изглед: две страници" [rightRail] closeSelected = "Затвори избраните файлове" @@ -3877,6 +3984,7 @@ toggleSidebar = "Показване/скриване на страничната exportSelected = "Експорт на избраните страници" toggleAnnotations = "Показване/скриване на анотациите" annotationMode = "Превключи режим на анотации" +print = "Печат на PDF" draw = "Рисуване" save = "Запази" saveChanges = "Запази промените" @@ -4494,6 +4602,7 @@ description = "URL или име на файл към импресум (задъ title = "Премиум и Enterprise" description = "Конфигурирайте вашия премиум или enterprise лицензионен ключ." license = "Конфигурация на лиценз" +noInput = "Моля, предоставете лицензен ключ или файл" [admin.settings.premium.licenseKey] toggle = "Имате лицензен ключ или сертификат?" @@ -4511,6 +4620,25 @@ line1 = "Презаписването на текущия лицензен кл line2 = "Предишният лиценз ще бъде окончателно загубен, освен ако не сте го архивирали другаде." line3 = "Важно: Пазете лицензните ключове поверителни и сигурни. Никога не ги споделяйте публично." +[admin.settings.premium.inputMethod] +text = "Лицензен ключ" +file = "Файл със сертификат" + +[admin.settings.premium.file] +label = "Файл с лицензен сертификат" +description = "Качете вашия .lic или .cert лицензен файл от офлайн покупки" +choose = "Изберете лицензен файл" +selected = "Избрано: {{filename}} ({{size}})" +successMessage = "Лицензният файл беше качен и активиран успешно. Не е необходимо рестартиране." + +[admin.settings.premium.currentLicense] +title = "Активен лиценз" +file = "Източник: Лицензен файл ({{path}})" +key = "Източник: Лицензен ключ" +type = "Тип: {{type}}" +noInput = "Моля, предоставете лицензен ключ или качете файл със сертификат" +success = "Успешно" + [admin.settings.premium.enabled] label = "Активирай премиум функции" description = "Активира проверки на лицензионния ключ за pro/enterprise функции" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} избрани" download = "Изтегли" delete = "Изтрий" unsupported = "Неподдържано" +active = "Активен" addToUpload = "Добави към качването" +closeFile = "Затвори файла" deleteAll = "Изтрий всички" loadingFiles = "Зареждане на файлове..." noFiles = "Няма налични файлове" @@ -5132,7 +5262,7 @@ upgrade = "Надградете сега →" freeTitle = "Лиценз за сървър" overLimitTitle = "Необходим е лиценз за сървър" overLimitBody = "Нашият лиценз позволява до {{freeTierLimit}} безплатни потребители на сървър. Имате {{overLimitUserCopy}} потребители на Stirling. За да продължите без прекъсвания, надградете до плана Stirling Server – неограничени места, редакция на PDF текст и пълен админ контрол за $99/сървър/месец." -freeBody = "Нашият Open-Core лиценз позволява до {{freeTierLimit}} безплатни потребители на сървър. За да мащабирате без прекъсвания и да получите ранен достъп до нашия нов инструмент за редакция на PDF текст, препоръчваме плана Stirling Server – пълно редактиране и неограничени места за $99/сървър/месец." +freeBody = "Нашият лицензен модел Open-Core позволява до {{freeTierLimit}} потребители безплатно на сървър. За безпрепятствено мащабиране препоръчваме плана Stirling Server - неограничени места и поддръжка на SSO за $99/server/mo." [onboarding.desktopInstall] title = "Изтегляне" @@ -5237,6 +5367,31 @@ error = "Неуспешно обновяване на статус на потр success = "Потребителят е изтрит успешно" error = "Неуспешно изтриване на потребител" +[workspace.people.changePassword] +action = "Промяна на парола" +title = "Промяна на парола" +subtitle = "Актуализирайте паролата за" +newPassword = "Нова парола" +confirmPassword = "Потвърдете паролата" +placeholder = "Въведете нова парола" +confirmPlaceholder = "Въведете отново новата парола" +passwordRequired = "Моля, въведете нова парола" +passwordMismatch = "Паролите не съвпадат" +generateRandom = "Генерирайте сигурна парола" +generatedPreview = "Генерирана парола:" +copyTooltip = "Копиране в клипборда" +copiedToClipboard = "Паролата е копирана в клипборда" +copyFailed = "Неуспешно копиране на паролата" +sendEmail = "Изпратете имейл на потребителя за тази промяна" +includePassword = "Включете новата парола в имейла" +forcePasswordChange = "Принудете потребителя да смени паролата при следващо влизане" +emailUnavailable = "Имейлът на този потребител не е валиден адрес. Известията са изключени." +smtpDisabled = "Имейл известията изискват SMTP да е активиран в настройките." +notifyOnly = "Ще бъде изпратен имейл без паролата, за да уведоми потребителя, че администратор я е променил." +submit = "Актуализиране на паролата" +success = "Паролата е актуализирана успешно" +error = "Неуспешно актуализиране на паролата" + [workspace.people.emailInvite] tab = "Покана по имейл" description = "Въведете или поставете имейли по-долу, разделени със запетаи. Потребителите ще получат данни за вход по имейл." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Изисква се поне един имейл адрес" submit = "Изпрати покани" success = "Потребител(и) поканени успешно" -partialSuccess = "Някои покани не успяха" +partialFailure = "Някои покани бяха неуспешни" allFailed = "Неуспешно канене на потребители" error = "Неуспешно изпращане на покани" @@ -5770,6 +5925,7 @@ subtitle = "Впишете се с вашия Stirling акаунт" [setup.selfhosted] title = "Впишете се в сървъра" subtitle = "Въведете своите данни за сървъра" +link = "или се свържете със самостоятелно хостван акаунт" [setup.server] title = "Свързване към сървър" @@ -5788,6 +5944,14 @@ description = "Въведете пълния URL на вашия самосто emptyUrl = "Моля, въведете URL на сървър" unreachable = "Неуспешна връзка със сървъра" testFailed = "Тестът на връзката е неуспешен" +configFetch = "Неуспешно извличане на конфигурацията на сървъра. Моля, проверете URL адреса и опитайте отново." + +[setup.server.error.securityDisabled] +title = "Входът не е активиран" +body = "На този сървър не е активиран вход. За да се свържете, трябва да активирате удостоверяване:" +step1 = "Задайте DOCKER_ENABLE_SECURITY=true във вашата среда" +step2 = "Или задайте security.enableLogin=true в settings.yml" +step3 = "Рестартирайте сървъра" [setup.login] title = "Вписване" @@ -5797,6 +5961,13 @@ submit = "Вход" signInWith = "Вписване с" oauthPending = "Отваряне на браузър за удостоверяване..." orContinueWith = "Или продължете с имейл" +serverRequirement = "Забележка: Сървърът трябва да има активиран вход." +showInstructions = "Как да се активира?" +hideInstructions = "Скрий инструкциите" +instructions = "За да активирате вход на вашия Stirling PDF сървър:" +instructionsEnvVar = "Задайте променливата на средата:" +instructionsOrYml = "Или в settings.yml:" +instructionsRestart = "След това рестартирайте сървъра, за да влязат промените в сила." [setup.login.username] label = "Потребителско име" @@ -5853,6 +6024,7 @@ earlyAccess = "Ранен достъп" reset = "Отмени промените" downloadJson = "Изтегли JSON" generatePdf = "Генерирай PDF" +saveChanges = "Запази промените" [pdfTextEditor.options.autoScaleText] title = "Авто-мащабиране на текст за напасване в полетата" @@ -5890,6 +6062,8 @@ alpha = "Този алфа визуализатор все още се разв [pdfTextEditor.empty] title = "Няма зареден документ" subtitle = "Заредете PDF или JSON файл, за да започнете да редактирате текстовото съдържание." +dropzone = "Плъзнете и пуснете тук PDF или JSON файл или щракнете, за да прегледате" +dropzoneWithFiles = "Изберете файл от раздела Файлове или плъзнете и пуснете тук PDF или JSON файл, или щракнете, за да прегледате" [pdfTextEditor.welcomeBanner] title = "Добре дошли в PDF Text Editor (ранен достъп)" diff --git a/frontend/public/locales/ca-CA/translation.toml b/frontend/public/locales/ca-CA/translation.toml index 2d8bc23e3..00370a7d7 100644 --- a/frontend/public/locales/ca-CA/translation.toml +++ b/frontend/public/locales/ca-CA/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Elimina dels preferits" fullscreen = "Canvia al mode de pantalla completa" sidebar = "Canvia al mode de barra lateral" +[backendStartup] +notFoundTitle = "Backend no trobat" +retry = "Torneu-ho a intentar" +unreachable = "L'aplicació no pot connectar-se al backend ara mateix. Verifiqueu l'estat del backend i la connectivitat de xarxa i torneu-ho a intentar." + [zipWarning] title = "Fitxer ZIP gran" message = "Aquest ZIP conté {{count}} fitxers. Vols extreure'l igualment?" @@ -347,7 +352,7 @@ teams = "Equips" title = "Configuració" systemSettings = "Configuració del sistema" features = "Funcions" -endpoints = "Endpoints" +endpoints = "Punts finals" database = "Base de dades" advanced = "Avançat" @@ -556,7 +561,7 @@ totalEndpoints = "Total d'endpoints" totalVisits = "Total de visites" showing = "Mostrant" selectedVisits = "Visites seleccionades" -endpoint = "Endpoint" +endpoint = "Punt final" visits = "Visites" percentage = "Percentatge" loading = "Carregant..." @@ -912,6 +917,9 @@ desc = "Construeix fluxos de treball multietapa enllaçant accions PDF. Ideal pe desc = "Superposa PDFs sobre un altre PDF" title = "Superposar PDFs" +[home.pdfTextEditor] +title = "Editor de text PDF" +desc = "Edita el text i les imatges existents dins dels PDF" [home.addText] tags = "text,anotació,etiqueta" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Signatura dibuixada" defaultImageLabel = "Signatura pujada" defaultTextLabel = "Signatura teclejada" saveButton = "Desa la signatura" +savePersonal = "Desa com a personal" +saveShared = "Desa com a compartida" saveUnavailable = "Crea una signatura primer per poder-la desar." noChanges = "La signatura actual ja està desada." +tempStorageTitle = "Emmagatzematge temporal del navegador" +tempStorageDescription = "Les signatures només s'emmagatzemen al vostre navegador. Es perdran si netegeu les dades del navegador o canvieu de navegador." +personalHeading = "Signatures personals" +sharedHeading = "Signatures compartides" +personalDescription = "Només vosaltres podeu veure aquestes signatures." +sharedDescription = "Tots els usuaris poden veure i utilitzar aquestes signatures." [sign.saved.type] canvas = "Dibuix" @@ -3020,6 +3036,91 @@ title = "Obteniu Informació del PDF" header = "Obteniu Informació del PDF" submit = "Obteniu Informació" downloadJson = "Descarrega JSON" +processing = "Extraient informació..." +results = "Resultats" +noResults = "Executeu l'eina per generar un informe." +downloads = "Descàrregues" +noneDetected = "No se n'ha detectat cap" +indexTitle = "Índex" + +[getPdfInfo.report] +entryLabel = "Resum d'informació complet" +shortTitle = "Informació del PDF" + +[getPdfInfo.sections] +metadata = "Metadades" +formFields = "Camps de formulari" +basicInfo = "Informació bàsica" +documentInfo = "Informació del document" +compliance = "Conformitat" +encryption = "Xifratge" +permissions = "Permisos" +other = "Altres" +perPageInfo = "Informació per pàgina" +tableOfContents = "Taula de continguts" + +[getPdfInfo.other] +attachments = "Fitxers adjunts" +embeddedFiles = "Fitxers incrustats" +javaScript = "JavaScript" +layers = "Capes" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Mida" +annotations = "Anotacions" +images = "Imatges" +links = "Enllaços" +fonts = "Tipus de lletra" +xobjects = "Recompte d'XObject" +multimedia = "Multimèdia" + +[getPdfInfo.summary] +pages = "Pàgines" +fileSize = "Mida del fitxer" +pdfVersion = "Versió del PDF" +language = "Idioma" +title = "Resum del PDF" +author = "Autor" +created = "Creat" +modified = "Modificat" +permsAll = "Tots els permisos permesos" +permsRestricted = "{{count}} restriccions" +permsMixed = "Alguns permisos restringits" +hasCompliance = "Té estàndards de conformitat" +noCompliance = "Sense estàndards de conformitat" +basic = "Informació bàsica" +documentInfo = "Informació del document" +securityTitle = "Estat de seguretat" +technical = "Tècnic" +overviewTitle = "Visió general del PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF xifrat - Protecció amb contrasenya present" +unencrypted = "PDF no xifrat - Sense protecció amb contrasenya" + +[getPdfInfo.summary.tech] +images = "Imatges" +fonts = "Tipus de lletra" +formFields = "Camps de formulari" +embeddedFiles = "Fitxers incrustats" +javaScript = "JavaScript" +layers = "Capes" +bookmarks = "Marcadors" +multimedia = "Multimèdia" + +[getPdfInfo.summary.overview] +untitled = "un document sense títol" +unknown = "Autor desconegut" +text = "Aquest és un PDF de {{pages}} pàgines titulat {{title}} creat per {{author}} (versió del PDF {{version}})." + +[getPdfInfo.error] +partial = "Alguns fitxers no s'han pogut processar." +unexpected = "Error inesperat durant l'extracció." + +[getPdfInfo.status] +complete = "Extracció completada" [extractPage] tags = "extreure" @@ -3438,6 +3539,9 @@ signinTitle = "Autenticat" ssoSignIn = "Inicia sessió mitjançant inici de sessió únic" oAuth2AutoCreateDisabled = "La creació automàtica d'usuaris OAUTH2 està desactivada" oAuth2AdminBlockedUser = "El registre o inici de sessió d'usuaris no registrats està actualment bloquejat. Si us plau, contacta amb l'administrador." +oAuth2RequiresLicense = "L'inici de sessió OAuth/SSO requereix una llicència de pagament (Server o Enterprise). Poseu-vos en contacte amb l'administrador per actualitzar el vostre pla." +saml2RequiresLicense = "L'inici de sessió SAML requereix una llicència de pagament (Server o Enterprise). Poseu-vos en contacte amb l'administrador per actualitzar el vostre pla." +maxUsersReached = "S'ha assolit el nombre màxim d'usuaris de la vostra llicència actual. Poseu-vos en contacte amb l'administrador per actualitzar el vostre pla o afegir més places." oauth2RequestNotFound = "Sol·licitud d'autorització no trobada" oauth2InvalidUserInfoResponse = "Resposta d'informació d'usuari no vàlida" oauth2invalidRequest = "Sol·licitud no vàlida" @@ -3846,14 +3950,17 @@ fitToWidth = "Ajusta a l'amplada" actualSize = "Mida real" [viewer] +cannotPreviewFile = "No es pot previsualitzar el fitxer" +dualPageView = "Vista de dues pàgines" firstPage = "Primera pàgina" lastPage = "Última pàgina" -previousPage = "Pàgina anterior" nextPage = "Pàgina següent" +onlyPdfSupported = "El visualitzador només admet fitxers PDF. Aquest fitxer sembla ser d'un format diferent." +previousPage = "Pàgina anterior" +singlePageView = "Vista d'una sola pàgina" +unknownFile = "Fitxer desconegut" zoomIn = "Amplia" zoomOut = "Redueix" -singlePageView = "Vista d'una sola pàgina" -dualPageView = "Vista de dues pàgines" [rightRail] closeSelected = "Tanca els fitxers seleccionats" @@ -3877,6 +3984,7 @@ toggleSidebar = "Mostra/oculta la barra lateral" exportSelected = "Exporta les pàgines seleccionades" toggleAnnotations = "Mostra/oculta les anotacions" annotationMode = "Activa/desactiva el mode d'anotació" +print = "Imprimeix el PDF" draw = "Dibuixa" save = "Desa" saveChanges = "Desa els canvis" @@ -3925,7 +4033,7 @@ files = "Fitxers" activity = "Registre" help = "Ajuda" account = "Compte" -config = "Config" +config = "Configuració" settings = "Ajustos" adminSettings = "Ajustos admin" allTools = "All Tools" @@ -4343,7 +4451,7 @@ features = "Banderes de funcions" processing = "Processament" [admin.settings.advanced.endpoints] -label = "Endpoints" +label = "Punts finals" manage = "Gestiona els endpoints de l'API" description = "La gestió d'endpoints es configura via YAML. Consulteu la documentació per a detalls sobre com habilitar/deshabilitar endpoints específics." @@ -4494,6 +4602,7 @@ description = "URL o nom de fitxer de l'impressum (requerit en algunes jurisdicc title = "Premium i Enterprise" description = "Configureu la clau de llicència Premium o Enterprise." license = "Configuració de llicència" +noInput = "Proporcioneu una clau de llicència o un fitxer" [admin.settings.premium.licenseKey] toggle = "Tens una clau de llicència o un fitxer de certificat?" @@ -4511,6 +4620,25 @@ line1 = "Sobreescriure la clau de llicència actual no es pot desfer." line2 = "La llicència anterior es perdrà permanentment si no en tens una còpia de seguretat." line3 = "Important: mantén les claus de llicència privades i segures. No les comparteixis mai públicament." +[admin.settings.premium.inputMethod] +text = "Clau de llicència" +file = "Fitxer de certificat" + +[admin.settings.premium.file] +label = "Fitxer de certificat de llicència" +description = "Pugeu el vostre fitxer de llicència .lic o .cert de compres fora de línia" +choose = "Trieu el fitxer de llicència" +selected = "Seleccionat: {{filename}} ({{size}})" +successMessage = "Fitxer de llicència pujat i activat correctament. No cal reiniciar." + +[admin.settings.premium.currentLicense] +title = "Llicència activa" +file = "Origen: Fitxer de llicència ({{path}})" +key = "Origen: Clau de llicència" +type = "Tipus: {{type}}" +noInput = "Proporcioneu una clau de llicència o pugeu un fitxer de certificat" +success = "Èxit" + [admin.settings.premium.enabled] label = "Habilita les funcions Premium" description = "Habilita les comprovacions de clau per a funcions pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} seleccionats" download = "Descarrega" delete = "Esborra" unsupported = "No compatible" +active = "Actiu" addToUpload = "Afegeix a la pujada" +closeFile = "Tanca el fitxer" deleteAll = "Suprimeix-ho tot" loadingFiles = "Carregant fitxers..." noFiles = "No hi ha fitxers disponibles" @@ -5132,7 +5262,7 @@ upgrade = "Actualitza ara →" freeTitle = "Llicència del servidor" overLimitTitle = "Cal una llicència de servidor" overLimitBody = "La nostra llicència permet fins a {{freeTierLimit}} usuaris gratuïts per servidor. Tens {{overLimitUserCopy}} usuaris de Stirling. Per continuar sense interrupcions, actualitza al pla Stirling Server: seients il·limitats, edició de text de PDF i control d'administració complet per 99 $/servidor/mes." -freeBody = "La nostra llicència Open-Core permet fins a {{freeTierLimit}} usuaris gratuïts per servidor. Per escalar sense interrupcions i obtenir accés anticipat a la nova eina d'edició de text PDF, recomanem el pla Stirling Server: edició completa i seients il·limitats per 99 $/servidor/mes." +freeBody = "La nostra llicència Open-Core permet fins a {{freeTierLimit}} usuaris gratuïts per servidor. Per escalar sense interrupcions, recomanem el pla Stirling Server - places il·limitades i suport SSO per $99/servidor/mes." [onboarding.desktopInstall] title = "Baixa" @@ -5237,6 +5367,31 @@ error = "No s’ha pogut actualitzar l’estat de l’usuari" success = "Usuari suprimit correctament" error = "No s’ha pogut suprimir l’usuari" +[workspace.people.changePassword] +action = "Canvieu la contrasenya" +title = "Canvi de contrasenya" +subtitle = "Actualitza la contrasenya de" +newPassword = "Contrasenya nova" +confirmPassword = "Confirma la contrasenya" +placeholder = "Introduïu una contrasenya nova" +confirmPlaceholder = "Torneu a introduir la contrasenya nova" +passwordRequired = "Introduïu una contrasenya nova" +passwordMismatch = "Les contrasenyes no coincideixen" +generateRandom = "Genereu una contrasenya segura" +generatedPreview = "Contrasenya generada:" +copyTooltip = "Copieu al portapapers" +copiedToClipboard = "Contrasenya copiada al portapapers" +copyFailed = "No s'ha pogut copiar la contrasenya" +sendEmail = "Envieu un correu a l'usuari sobre aquest canvi" +includePassword = "Incloeu la contrasenya nova al correu" +forcePasswordChange = "Obligueu l'usuari a canviar la contrasenya en el pròxim inici de sessió" +emailUnavailable = "El correu d'aquest usuari no és una adreça de correu vàlida. Les notificacions estan desactivades." +smtpDisabled = "Les notificacions per correu electrònic requereixen habilitar SMTP als paràmetres." +notifyOnly = "S'enviarà un correu sense la contrasenya, informant l'usuari que un administrador l'ha canviada." +submit = "Actualitzeu la contrasenya" +success = "La contrasenya s'ha actualitzat correctament" +error = "No s'ha pogut actualitzar la contrasenya" + [workspace.people.emailInvite] tab = "Invitació per correu" description = "Escriviu o enganxeu correus a continuació, separats per comes. Els usuaris rebran credencials d’inici de sessió per correu electrònic." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Cal almenys una adreça de correu" submit = "Envia invitacions" success = "usuari(s) convidat(s) correctament" -partialSuccess = "Algunes invitacions han fallat" +partialFailure = "Algunes invitacions han fallat" allFailed = "No s’ha pogut convidar els usuaris" error = "No s’han pogut enviar les invitacions" @@ -5709,7 +5864,7 @@ title = "Gràfic d'ús dels endpoints" [usage.table] title = "Estadístiques detallades" -endpoint = "Endpoint" +endpoint = "Punt final" visits = "Visites" percentage = "Percentatge" noData = "No hi ha dades disponibles" @@ -5770,6 +5925,7 @@ subtitle = "Inicia sessió amb el teu compte de Stirling" [setup.selfhosted] title = "Inicia sessió al servidor" subtitle = "Introdueix les credencials del servidor" +link = "o connecteu-vos a un compte autoallotjat" [setup.server] title = "Connecta't al servidor" @@ -5788,6 +5944,14 @@ description = "Introdueix la URL completa del teu servidor autoallotjat de Stirl emptyUrl = "Introdueix una URL de servidor" unreachable = "No s'ha pogut connectar amb el servidor" testFailed = "Ha fallat la prova de connexió" +configFetch = "No s'ha pogut obtenir la configuració del servidor. Comproveu l'URL i torneu-ho a provar." + +[setup.server.error.securityDisabled] +title = "Inici de sessió no habilitat" +body = "Aquest servidor no té l'inici de sessió habilitat. Per connectar-hi, heu d'habilitar l'autenticació:" +step1 = "Establiu DOCKER_ENABLE_SECURITY=true al vostre entorn" +step2 = "O establiu security.enableLogin=true a settings.yml" +step3 = "Reinicieu el servidor" [setup.login] title = "Inicia sessió" @@ -5797,6 +5961,13 @@ submit = "Inicia sessió" signInWith = "Inicia sessió amb" oauthPending = "Obrint el navegador per autenticar-te..." orContinueWith = "O continua amb el correu electrònic" +serverRequirement = "Nota: el servidor ha de tenir l'inici de sessió habilitat." +showInstructions = "Com s'habilita?" +hideInstructions = "Amagueu les instruccions" +instructions = "Per habilitar l'inici de sessió al vostre servidor de Stirling PDF:" +instructionsEnvVar = "Establiu la variable d'entorn:" +instructionsOrYml = "O a settings.yml:" +instructionsRestart = "A continuació, reinicieu el servidor perquè els canvis tinguin efecte." [setup.login.username] label = "Nom d'usuari" @@ -5853,6 +6024,7 @@ earlyAccess = "Accés anticipat" reset = "Restableix els canvis" downloadJson = "Descarrega JSON" generatePdf = "Genera PDF" +saveChanges = "Deseu els canvis" [pdfTextEditor.options.autoScaleText] title = "Autoajusta el text a les caixes" @@ -5890,6 +6062,8 @@ alpha = "Aquest visor alfa encara evoluciona—certs tipus de lletra, colors, ef [pdfTextEditor.empty] title = "No s'ha carregat cap document" subtitle = "Carrega un fitxer PDF o JSON per començar a editar el contingut de text." +dropzone = "Arrossegueu i deixeu anar un fitxer PDF o JSON aquí, o feu clic per explorar" +dropzoneWithFiles = "Seleccioneu un fitxer de la pestanya Fitxers o arrossegueu i deixeu anar aquí un fitxer PDF o JSON, o feu clic per explorar" [pdfTextEditor.welcomeBanner] title = "Benvingut a l'Editor de text PDF (accés anticipat)" diff --git a/frontend/public/locales/cs-CZ/translation.toml b/frontend/public/locales/cs-CZ/translation.toml index 0f80dd14b..54d1d8728 100644 --- a/frontend/public/locales/cs-CZ/translation.toml +++ b/frontend/public/locales/cs-CZ/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Odebrat z oblíbených" fullscreen = "Přepnout na režim na celou obrazovku" sidebar = "Přepnout na režim postranního panelu" +[backendStartup] +notFoundTitle = "Backend nebyl nalezen" +retry = "Zkusit znovu" +unreachable = "Aplikace se nyní nemůže připojit k backendu. Ověřte stav backendu a síťové připojení a poté to zkuste znovu." + [zipWarning] title = "Velký soubor ZIP" message = "Tento ZIP obsahuje {{count}} souborů. Přesto rozbalit?" @@ -347,7 +352,7 @@ teams = "Týmy" title = "Konfigurace" systemSettings = "Systémová nastavení" features = "Funkce" -endpoints = "Endpoints" +endpoints = "Koncové body" database = "Databáze" advanced = "Pokročilé" @@ -912,6 +917,9 @@ desc = "Vytvářejte vícekrokové workflow řetězením akcí PDF. Ideální pr desc = "Překryje PDF nad jiným PDF" title = "Překrýt PDF" +[home.pdfTextEditor] +title = "Editor textu PDF" +desc = "Upravujte existující text a obrázky v PDF" [home.addText] tags = "text,anotace,štítek" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Nakreslený podpis" defaultImageLabel = "Nahraný podpis" defaultTextLabel = "Napsaný podpis" saveButton = "Uložit podpis" +savePersonal = "Uložit osobní" +saveShared = "Uložit sdílené" saveUnavailable = "Nejprve vytvořte podpis, abyste jej mohli uložit." noChanges = "Aktuální podpis je již uložen." +tempStorageTitle = "Dočasné úložiště prohlížeče" +tempStorageDescription = "Podpisy jsou uloženy pouze ve vašem prohlížeči. Při vymazání dat prohlížeče nebo při přepnutí na jiný prohlížeč budou ztraceny." +personalHeading = "Osobní podpisy" +sharedHeading = "Sdílené podpisy" +personalDescription = "Tyto podpisy vidíte pouze vy." +sharedDescription = "Všichni uživatelé mohou tyto podpisy vidět a používat." [sign.saved.type] canvas = "Kresba" @@ -3020,6 +3036,91 @@ title = "Získat informace o PDF" header = "Získat informace o PDF" submit = "Získat informace" downloadJson = "Stáhnout JSON" +processing = "Probíhá extrahování informací..." +results = "Výsledky" +noResults = "Spusťte nástroj pro vygenerování zprávy." +downloads = "Stažení" +noneDetected = "Nic nebylo zjištěno" +indexTitle = "Rejstřík" + +[getPdfInfo.report] +entryLabel = "Úplné shrnutí informací" +shortTitle = "Informace o PDF" + +[getPdfInfo.sections] +metadata = "Metadata" +formFields = "Formulářová pole" +basicInfo = "Základní informace" +documentInfo = "Informace o dokumentu" +compliance = "Shoda" +encryption = "Šifrování" +permissions = "Oprávnění" +other = "Ostatní" +perPageInfo = "Informace po stránkách" +tableOfContents = "Obsah" + +[getPdfInfo.other] +attachments = "Přílohy" +embeddedFiles = "Vložené soubory" +javaScript = "JavaScript" +layers = "Vrstvy" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Velikost" +annotations = "Anotace" +images = "Obrázky" +links = "Odkazy" +fonts = "Písma" +xobjects = "Počty XObjectů" +multimedia = "Multimédia" + +[getPdfInfo.summary] +pages = "Stránky" +fileSize = "Velikost souboru" +pdfVersion = "Verze PDF" +language = "Jazyk" +title = "Souhrn PDF" +author = "Autor" +created = "Vytvořeno" +modified = "Upraveno" +permsAll = "Všechna oprávnění povolena" +permsRestricted = "{{count}} omezení" +permsMixed = "Některá oprávnění jsou omezena" +hasCompliance = "Obsahuje standardy shody" +noCompliance = "Žádné standardy shody" +basic = "Základní informace" +documentInfo = "Informace o dokumentu" +securityTitle = "Stav zabezpečení" +technical = "Technické" +overviewTitle = "Přehled PDF" + +[getPdfInfo.summary.security] +encrypted = "Šifrované PDF – chráněno heslem" +unencrypted = "Nešifrované PDF – bez ochrany heslem" + +[getPdfInfo.summary.tech] +images = "Obrázky" +fonts = "Písma" +formFields = "Formulářová pole" +embeddedFiles = "Vložené soubory" +javaScript = "JavaScript" +layers = "Vrstvy" +bookmarks = "Záložky" +multimedia = "Multimédia" + +[getPdfInfo.summary.overview] +untitled = "nepojmenovaný dokument" +unknown = "Neznámý autor" +text = "Toto je PDF o {{pages}} stránkách s názvem {{title}} od autora {{author}} (verze PDF {{version}})." + +[getPdfInfo.error] +partial = "Některé soubory se nepodařilo zpracovat." +unexpected = "Během extrahování došlo k neočekávané chybě." + +[getPdfInfo.status] +complete = "Extrahování dokončeno" [extractPage] tags = "extrahovat" @@ -3438,6 +3539,9 @@ signinTitle = "Prosím přihlaste se" ssoSignIn = "Přihlásit se přes Single Sign-on" oAuth2AutoCreateDisabled = "Automatické vytváření OAUTH2 uživatelů je zakázáno" oAuth2AdminBlockedUser = "Registrace nebo přihlášení neregistrovaných uživatelů je momentálně blokováno. Kontaktujte prosím správce." +oAuth2RequiresLicense = "Přihlášení pomocí OAuth/SSO vyžaduje placenou licenci (Server nebo Enterprise). Kontaktujte prosím administrátora kvůli upgradu vašeho plánu." +saml2RequiresLicense = "Přihlášení pomocí SAML vyžaduje placenou licenci (Server nebo Enterprise). Kontaktujte prosím administrátora kvůli upgradu vašeho plánu." +maxUsersReached = "Byl dosažen maximální počet uživatelů pro vaši aktuální licenci. Kontaktujte prosím administrátora kvůli upgradu vašeho plánu nebo přidání dalších míst." oauth2RequestNotFound = "Požadavek na autorizaci nebyl nalezen" oauth2InvalidUserInfoResponse = "Neplatná odpověď s informacemi o uživateli" oauth2invalidRequest = "Neplatný požadavek" @@ -3846,14 +3950,17 @@ fitToWidth = "Přizpůsobit šířce" actualSize = "Skutečná velikost" [viewer] +cannotPreviewFile = "Nelze zobrazit náhled souboru" +dualPageView = "Zobrazení dvou stránek" firstPage = "První stránka" lastPage = "Poslední stránka" -previousPage = "Předchozí stránka" nextPage = "Další stránka" +onlyPdfSupported = "Prohlížeč podporuje pouze soubory PDF. Tento soubor má zřejmě jiný formát." +previousPage = "Předchozí stránka" +singlePageView = "Zobrazení jedné stránky" +unknownFile = "Neznámý soubor" zoomIn = "Přiblížit" zoomOut = "Oddálit" -singlePageView = "Zobrazení jedné stránky" -dualPageView = "Zobrazení dvou stránek" [rightRail] closeSelected = "Zavřít vybrané soubory" @@ -3877,6 +3984,7 @@ toggleSidebar = "Přepnout postranní panel" exportSelected = "Exportovat vybrané stránky" toggleAnnotations = "Přepnout viditelnost anotací" annotationMode = "Přepnout režim anotací" +print = "Tisk PDF" draw = "Kreslit" save = "Uložit" saveChanges = "Uložit změny" @@ -4153,7 +4261,7 @@ description = "Sledovat akce uživatelů a systémové události pro compliance [admin.settings.security.audit.level] label = "Úroveň auditu" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=VYPNUTO, 1=ZÁKLADNÍ, 2=STANDARDNÍ, 3=PODROBNÝ" [admin.settings.security.audit.retentionDays] label = "Doba uchování auditů (dny)" @@ -4494,6 +4602,7 @@ description = "URL nebo název souboru k Impressu (vyžadováno v některých ju title = "Premium a Enterprise" description = "Nakonfigurujte svůj prémiový nebo enterprise licenční klíč." license = "Konfigurace licence" +noInput = "Zadejte licenční klíč nebo soubor" [admin.settings.premium.licenseKey] toggle = "Máte licenční klíč nebo certifikační soubor?" @@ -4511,6 +4620,25 @@ line1 = "Přepsání aktuálního licenčního klíče nelze vrátit zpět." line2 = "Předchozí licence bude trvale ztracena, pokud ji nemáte zálohovanou jinde." line3 = "Důležité: Uchovávejte licenční klíče v soukromí a v bezpečí. Nikdy je nesdílejte veřejně." +[admin.settings.premium.inputMethod] +text = "Licenční klíč" +file = "Soubor certifikátu" + +[admin.settings.premium.file] +label = "Soubor licenčního certifikátu" +description = "Nahrajte svůj licenční soubor .lic nebo .cert z offline nákupu" +choose = "Vybrat licenční soubor" +selected = "Vybráno: {{filename}} ({{size}})" +successMessage = "Licenční soubor byl úspěšně nahrán a aktivován. Restart není vyžadován." + +[admin.settings.premium.currentLicense] +title = "Aktivní licence" +file = "Zdroj: Licenční soubor ({{path}})" +key = "Zdroj: Licenční klíč" +type = "Typ: {{type}}" +noInput = "Zadejte licenční klíč nebo nahrajte soubor certifikátu" +success = "Úspěch" + [admin.settings.premium.enabled] label = "Povolit prémiové funkce" description = "Povolit kontrolu licenčního klíče pro pro/enterprise funkce" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} vybráno" download = "Stáhnout" delete = "Smazat" unsupported = "Nepodporováno" +active = "Aktivní" addToUpload = "Přidat k nahrání" +closeFile = "Zavřít soubor" deleteAll = "Smazat vše" loadingFiles = "Načítání souborů..." noFiles = "Nejsou k dispozici žádné soubory" @@ -5132,7 +5262,7 @@ upgrade = "Upgradovat nyní →" freeTitle = "Serverová licence" overLimitTitle = "Vyžadována serverová licence" overLimitBody = "Naše licencování umožňuje až {{freeTierLimit}} uživatelů zdarma na server. Máte {{overLimitUserCopy}} uživatelů Stirling. Pro nepřerušené používání přejděte na plán Stirling Server – neomezený počet míst, úpravy textu PDF a plná správa za 99 $/server/měsíc." -freeBody = "Naše licencování Open-Core umožňuje až {{freeTierLimit}} uživatelů zdarma na server. Pro nepřerušený růst a přednostní přístup k našemu novému nástroji pro úpravu textu PDF doporučujeme plán Stirling Server – plné úpravy a neomezený počet míst za 99 $/server/měsíc." +freeBody = "Naše licencování Open-Core umožňuje až {{freeTierLimit}} uživatelů zdarma na server. Pro nepřerušované škálování doporučujeme plán Stirling Server - neomezený počet míst a podpora SSO za $99/server/měs." [onboarding.desktopInstall] title = "Stáhnout" @@ -5237,6 +5367,31 @@ error = "Nepodařilo se aktualizovat stav uživatele" success = "Uživatel úspěšně smazán" error = "Nepodařilo se smazat uživatele" +[workspace.people.changePassword] +action = "Změnit heslo" +title = "Změna hesla" +subtitle = "Aktualizovat heslo pro" +newPassword = "Nové heslo" +confirmPassword = "Potvrzení hesla" +placeholder = "Zadejte nové heslo" +confirmPlaceholder = "Zadejte nové heslo znovu" +passwordRequired = "Zadejte prosím nové heslo" +passwordMismatch = "Hesla se neshodují" +generateRandom = "Vygenerovat bezpečné heslo" +generatedPreview = "Vygenerované heslo:" +copyTooltip = "Zkopírovat do schránky" +copiedToClipboard = "Heslo zkopírováno do schránky" +copyFailed = "Heslo se nepodařilo zkopírovat" +sendEmail = "Odeslat uživateli e-mail o této změně" +includePassword = "Zahrnout nové heslo do e-mailu" +forcePasswordChange = "Vynutit změnu hesla při příštím přihlášení" +emailUnavailable = "E-mailová adresa tohoto uživatele není platná. Oznámení jsou deaktivována." +smtpDisabled = "E-mailová oznámení vyžadují, aby bylo v nastavení povoleno SMTP." +notifyOnly = "Bude odeslán e-mail bez hesla, který uživateli oznámí, že ho změnil administrátor." +submit = "Aktualizovat heslo" +success = "Heslo bylo úspěšně aktualizováno" +error = "Heslo se nepodařilo aktualizovat" + [workspace.people.emailInvite] tab = "Pozvánka e‑mailem" description = "Níže napište nebo vložte e‑maily oddělené čárkami. Uživatelé obdrží přihlašovací údaje e‑mailem." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "uzivatel1@priklad.cz, uzivatel2@priklad.cz" emailsRequired = "Je vyžadována alespoň jedna e‑mailová adresa" submit = "Odeslat pozvánky" success = "uživatel(é) úspěšně pozváni" -partialSuccess = "Některé pozvánky se nepodařilo odeslat" +partialFailure = "Některá pozvání selhala" allFailed = "Nepodařilo se pozvat uživatele" error = "Nepodařilo se odeslat pozvánky" @@ -5709,7 +5864,7 @@ title = "Graf využití endpointů" [usage.table] title = "Podrobné statistiky" -endpoint = "Endpoint" +endpoint = "Koncový bod" visits = "Návštěvy" percentage = "Procenta" noData = "Žádná data nejsou k dispozici" @@ -5770,6 +5925,7 @@ subtitle = "Přihlaste se svým účtem Stirling" [setup.selfhosted] title = "Přihlásit se k serveru" subtitle = "Zadejte přihlašovací údaje k vašemu serveru" +link = "nebo se připojte k účtu s vlastním hostováním" [setup.server] title = "Připojit k serveru" @@ -5788,6 +5944,14 @@ description = "Zadejte úplnou URL vašeho samohostovaného serveru Stirling PDF emptyUrl = "Zadejte URL serveru" unreachable = "Nelze se připojit k serveru" testFailed = "Test připojení selhal" +configFetch = "Nepodařilo se načíst konfiguraci serveru. Zkontrolujte prosím URL a zkuste to znovu." + +[setup.server.error.securityDisabled] +title = "Přihlášení není povoleno" +body = "Na tomto serveru není povoleno přihlašování. Pokud se chcete připojit, musíte povolit ověřování:" +step1 = "Nastavte DOCKER_ENABLE_SECURITY=true ve svém prostředí" +step2 = "Nebo nastavte security.enableLogin=true v souboru settings.yml" +step3 = "Restartujte server" [setup.login] title = "Přihlášení" @@ -5797,6 +5961,13 @@ submit = "Přihlásit se" signInWith = "Přihlásit se pomocí" oauthPending = "Otevírám prohlížeč pro ověření..." orContinueWith = "Nebo pokračovat e-mailem" +serverRequirement = "Poznámka: Na serveru musí být povoleno přihlášení." +showInstructions = "Jak povolit?" +hideInstructions = "Skrýt pokyny" +instructions = "Chcete-li povolit přihlášení na vašem serveru Stirling PDF:" +instructionsEnvVar = "Nastavte proměnnou prostředí:" +instructionsOrYml = "Nebo v settings.yml:" +instructionsRestart = "Poté restartujte server, aby se změny projevily." [setup.login.username] label = "Uživatelské jméno" @@ -5840,7 +6011,7 @@ paragraph = "Odstavcová stránka" sparse = "Řídký text" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automaticky" paragraph = "Odstavec" singleLine = "Jeden řádek" @@ -5853,6 +6024,7 @@ earlyAccess = "Předběžný přístup" reset = "Obnovit změny" downloadJson = "Stáhnout JSON" generatePdf = "Vytvořit PDF" +saveChanges = "Uložit změny" [pdfTextEditor.options.autoScaleText] title = "Automaticky přizpůsobit text rámečkům" @@ -5890,6 +6062,8 @@ alpha = "Tento alfa prohlížeč se stále vyvíjí — některé fonty, barvy, [pdfTextEditor.empty] title = "Není načten žádný dokument" subtitle = "Načtěte soubor PDF nebo JSON a začněte upravovat text." +dropzone = "Sem přetáhněte soubor PDF nebo JSON, případně kliknutím vyberte" +dropzoneWithFiles = "Vyberte soubor na kartě Soubory, nebo sem přetáhněte soubor PDF či JSON, případně kliknutím vyberte" [pdfTextEditor.welcomeBanner] title = "Vítejte v editoru textu PDF (předběžný přístup)" @@ -5932,13 +6106,13 @@ warnings = "Varování" suggestions = "Poznámky" currentPageFonts = "Fonty na této stránce" allFonts = "Všechny fonty" -fallback = "fallback" +fallback = "náhradní" missing = "chybí" perfectMessage = "Všechny fonty lze reprodukovat dokonale." warningMessage = "Některé fonty se nemusí vykreslit správně." infoMessage = "K dispozici jsou informace o reprodukci fontů." -perfect = "perfect" -subset = "subset" +perfect = "dokonalé" +subset = "podmnožina" [pdfTextEditor.errors] invalidJson = "Nelze přečíst soubor JSON. Ujistěte se, že byl vytvořen nástrojem PDF to JSON." diff --git a/frontend/public/locales/da-DK/translation.toml b/frontend/public/locales/da-DK/translation.toml index bc44aa479..88e688291 100644 --- a/frontend/public/locales/da-DK/translation.toml +++ b/frontend/public/locales/da-DK/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Fjern fra favoritter" fullscreen = "Skift til fuldskærmstilstand" sidebar = "Skift til sidepanel-tilstand" +[backendStartup] +notFoundTitle = "Backend ikke fundet" +retry = "Prøv igen" +unreachable = "Programmet kan i øjeblikket ikke forbinde til backend. Kontroller backend-status og netværksforbindelse, og prøv igen." + [zipWarning] title = "Stor ZIP-fil" message = "Denne ZIP indeholder {{count}} filer. Udpak alligevel?" @@ -347,7 +352,7 @@ teams = "Teams" title = "Konfiguration" systemSettings = "Systemindstillinger" features = "Funktioner" -endpoints = "Endpoints" +endpoints = "Slutpunkter" database = "Database" advanced = "Avanceret" @@ -359,7 +364,7 @@ connections = "Forbindelser" [settings.licensingAnalytics] title = "Licensering & Analytics" plan = "Plan" -audit = "Audit" +audit = "Revision" usageAnalytics = "Brugsanalyse" [settings.policiesPrivacy] @@ -556,13 +561,13 @@ totalEndpoints = "Endpoints i alt" totalVisits = "Besøg i alt" showing = "Viser" selectedVisits = "Valgte besøg" -endpoint = "Endpoint" +endpoint = "Slutpunkt" visits = "Besøg" percentage = "Procent" loading = "Laster..." failedToLoad = "Kunne ikke indlæse endpoint-data. Prøv at opdatere." home = "Hjem" -login = "Login" +login = "Log ind" top = "Top" numberOfVisits = "Antal besøg" visitsTooltip = "Besøg: {0} ({1}% af totalen)" @@ -912,6 +917,9 @@ desc = "Byg flertrins-workflows ved at kæde PDF-handlinger sammen. Ideelt til t desc = "Overlejrer PDF'er oven på en anden PDF" title = "Overlejr PDF'er" +[home.pdfTextEditor] +title = "PDF-teksteditor" +desc = "Rediger eksisterende tekst og billeder i PDF'er" [home.addText] tags = "tekst,annotering,etiket" @@ -1173,7 +1181,7 @@ selectFilesPlaceholder = "Vælg filer i hovedvisningen for at komme i gang" settings = "Indstillinger" conversionCompleted = "Konvertering fuldført" results = "Resultater" -defaultFilename = "converted_file" +defaultFilename = "konverteret_fil" conversionResults = "Konverteringsresultater" convertFrom = "Konvertér fra" convertTo = "Konvertér til" @@ -1213,9 +1221,9 @@ pdfaDigitalSignatureWarning = "PDF'en indeholder en digital signatur. Dette vil fileFormat = "Filformat" wordDoc = "Word-dokument" wordDocExt = "Word-dokument (.docx)" -odtExt = "OpenDocument Text (.odt)" +odtExt = "OpenDocument-tekst (.odt)" pptExt = "PowerPoint (.pptx)" -odpExt = "OpenDocument Presentation (.odp)" +odpExt = "OpenDocument-præsentation (.odp)" txtExt = "Almindelig tekst (.txt)" rtfExt = "Rich Text Format (.rtf)" selectedFiles = "Valgte filer" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Tegnet signatur" defaultImageLabel = "Uploadet signatur" defaultTextLabel = "Indtastet signatur" saveButton = "Gem signatur" +savePersonal = "Gem personlig" +saveShared = "Gem delt" saveUnavailable = "Opret først en signatur for at gemme den." noChanges = "Nuværende signatur er allerede gemt." +tempStorageTitle = "Midlertidig browserlagring" +tempStorageDescription = "Signaturer gemmes kun i din browser. De går tabt, hvis du rydder browserdata eller skifter browser." +personalHeading = "Personlige signaturer" +sharedHeading = "Delte signaturer" +personalDescription = "Kun du kan se disse signaturer." +sharedDescription = "Alle brugere kan se og bruge disse signaturer." [sign.saved.type] canvas = "Tegning" @@ -3020,6 +3036,91 @@ title = "Få Info om PDF" header = "Få Info om PDF" submit = "Få Info" downloadJson = "Download JSON" +processing = "Udtrækker oplysninger..." +results = "Resultater" +noResults = "Kør værktøjet for at generere en rapport." +downloads = "Downloads" +noneDetected = "Ingen registreret" +indexTitle = "Indeks" + +[getPdfInfo.report] +entryLabel = "Fuldt informationsresumé" +shortTitle = "PDF-oplysninger" + +[getPdfInfo.sections] +metadata = "Metadata" +formFields = "Formularfelter" +basicInfo = "Grundlæggende info" +documentInfo = "Dokumentinfo" +compliance = "Overensstemmelse" +encryption = "Kryptering" +permissions = "Tilladelser" +other = "Andet" +perPageInfo = "Info pr. side" +tableOfContents = "Indholdsfortegnelse" + +[getPdfInfo.other] +attachments = "Vedhæftninger" +embeddedFiles = "Indlejrede filer" +javaScript = "JavaScript" +layers = "Lag" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Størrelse" +annotations = "Anmærkninger" +images = "Billeder" +links = "Links" +fonts = "Skrifttyper" +xobjects = "Antal XObjects" +multimedia = "Multimedie" + +[getPdfInfo.summary] +pages = "Sider" +fileSize = "Filstørrelse" +pdfVersion = "PDF-version" +language = "Sprog" +title = "PDF-resumé" +author = "Forfatter" +created = "Oprettet" +modified = "Ændret" +permsAll = "Alle tilladelser tilladt" +permsRestricted = "{{count}} begrænsninger" +permsMixed = "Nogle tilladelser er begrænsede" +hasCompliance = "Har overensstemmelsesstandarder" +noCompliance = "Ingen overensstemmelsesstandarder" +basic = "Grundlæggende oplysninger" +documentInfo = "Dokumentoplysninger" +securityTitle = "Sikkerhedsstatus" +technical = "Teknisk" +overviewTitle = "PDF-oversigt" + +[getPdfInfo.summary.security] +encrypted = "Krypteret PDF - med adgangskodebeskyttelse" +unencrypted = "Ukrypteret PDF - ingen adgangskodebeskyttelse" + +[getPdfInfo.summary.tech] +images = "Billeder" +fonts = "Skrifttyper" +formFields = "Formularfelter" +embeddedFiles = "Indlejrede filer" +javaScript = "JavaScript" +layers = "Lag" +bookmarks = "Bogmærker" +multimedia = "Multimedie" + +[getPdfInfo.summary.overview] +untitled = "et dokument uden titel" +unknown = "Ukendt forfatter" +text = "Dette er en PDF på {{pages}} sider med titlen {{title}}, oprettet af {{author}} (PDF-version {{version}})." + +[getPdfInfo.error] +partial = "Nogle filer kunne ikke behandles." +unexpected = "Uventet fejl under udtrækning." + +[getPdfInfo.status] +complete = "Udtrækning fuldført" [extractPage] tags = "udtræk" @@ -3438,6 +3539,9 @@ signinTitle = "Log venligst ind" ssoSignIn = "Log ind via Single Sign-on" oAuth2AutoCreateDisabled = "OAUTH2 Auto-Opret Bruger Deaktiveret" oAuth2AdminBlockedUser = "Registrering eller login af ikke-registrerede brugere er i øjeblikket blokeret. Kontakt venligst administratoren." +oAuth2RequiresLicense = "OAuth/SSO-login kræver en betalt licens (Server eller Enterprise). Kontakt administratoren for at opgradere din plan." +saml2RequiresLicense = "SAML-login kræver en betalt licens (Server eller Enterprise). Kontakt administratoren for at opgradere din plan." +maxUsersReached = "Maksimalt antal brugere er nået for din nuværende licens. Kontakt administratoren for at opgradere din plan eller tilføje flere pladser." oauth2RequestNotFound = "Autorisationsanmodning ikke fundet" oauth2InvalidUserInfoResponse = "Ugyldigt Brugerinfo Svar" oauth2invalidRequest = "Ugyldig Anmodning" @@ -3771,7 +3875,7 @@ version = "Nuværende udgivelse" title = "API-dokumentation" header = "API-dokumentation" desc = "Se og test Stirling PDF API-endpoints" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,dokumentation,swagger,endepunkter,udvikling" [cookieBanner.popUp] title = "Sådan bruger vi cookies" @@ -3846,14 +3950,17 @@ fitToWidth = "Tilpas til bredde" actualSize = "Faktisk størrelse" [viewer] +cannotPreviewFile = "Kan ikke forhåndsvise fil" +dualPageView = "To-siders visning" firstPage = "Første side" lastPage = "Sidste side" -previousPage = "Forrige side" nextPage = "Næste side" +onlyPdfSupported = "Visningen understøtter kun PDF-filer. Denne fil ser ud til at være et andet format." +previousPage = "Forrige side" +singlePageView = "Enkelt-sides visning" +unknownFile = "Ukendt fil" zoomIn = "Zoom ind" zoomOut = "Zoom ud" -singlePageView = "Enkelt-sides visning" -dualPageView = "To-siders visning" [rightRail] closeSelected = "Luk valgte filer" @@ -3877,6 +3984,7 @@ toggleSidebar = "Skift sidepanel" exportSelected = "Eksporter valgte sider" toggleAnnotations = "Skift visning af annoteringer" annotationMode = "Skift annoteringstilstand" +print = "Udskriv PDF" draw = "Tegn" save = "Gem" saveChanges = "Gem ændringer" @@ -4235,11 +4343,11 @@ label = "Issuer-URL" description = "OAuth2-udbyderens issuer-URL" [admin.settings.connections.oauth2.clientId] -label = "Client ID" +label = "Klient-ID" description = "OAuth2 Client ID fra din udbyder" [admin.settings.connections.oauth2.clientSecret] -label = "Client Secret" +label = "Klienthemmelighed" description = "OAuth2 Client Secret fra din udbyder" [admin.settings.connections.oauth2.useAsUsername] @@ -4343,7 +4451,7 @@ features = "Funktionsflag" processing = "Behandling" [admin.settings.advanced.endpoints] -label = "Endpoints" +label = "Slutpunkter" manage = "Administrer API-endpoints" description = "Endpointstyring konfigureres via YAML. Se dokumentationen for detaljer om aktivering/deaktivering af specifikke endpoints." @@ -4494,6 +4602,7 @@ description = "URL eller filnavn til impressum (påkrævet i nogle jurisdiktione title = "Premium og Enterprise" description = "Konfigurer din premium- eller enterprise-licensnøgle." license = "Licenskonfiguration" +noInput = "Angiv en licensnøgle eller fil" [admin.settings.premium.licenseKey] toggle = "Har du en licensnøgle eller en certifikatfil?" @@ -4511,6 +4620,25 @@ line1 = "Overskrivning af din nuværende licensnøgle kan ikke fortrydes." line2 = "Din tidligere licens går permanent tabt, medmindre du har sikkerhedskopieret den andetsteds." line3 = "Vigtigt: Hold licensnøgler private og sikre. Del dem aldrig offentligt." +[admin.settings.premium.inputMethod] +text = "Licensnøgle" +file = "Certifikatfil" + +[admin.settings.premium.file] +label = "Licenscertifikatfil" +description = "Upload din .lic- eller .cert-licensfil fra offlinekøb" +choose = "Vælg licensfil" +selected = "Valgt: {{filename}} ({{size}})" +successMessage = "Licensfil uploadet og aktiveret. Genstart er ikke påkrævet." + +[admin.settings.premium.currentLicense] +title = "Aktiv licens" +file = "Kilde: Licensfil ({{path}})" +key = "Kilde: Licensnøgle" +type = "Type: {{type}}" +noInput = "Angiv en licensnøgle eller upload en certifikatfil" +success = "Succes" + [admin.settings.premium.enabled] label = "Aktivér premium-funktioner" description = "Aktivér licensnøgletjek for pro-/enterprise-funktioner" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} valgt" download = "Download" delete = "Slet" unsupported = "Ikke understøttet" +active = "Aktiv" addToUpload = "Føj til upload" +closeFile = "Luk fil" deleteAll = "Slet alle" loadingFiles = "Indlæser filer..." noFiles = "Ingen filer tilgængelige" @@ -5132,7 +5262,7 @@ upgrade = "Opgrader nu →" freeTitle = "Serverlicens" overLimitTitle = "Serverlicens påkrævet" overLimitBody = "Vores licens tillader op til {{freeTierLimit}} brugere gratis pr. server. Du har {{overLimitUserCopy}} Stirling-brugere. For at fortsætte uden afbrydelser skal du opgradere til Stirling Server-abonnementet – ubegrænsede pladser, PDF-tekstredigering og fuld admin-kontrol for $99/server/md." -freeBody = "Vores Open-Core-licens tillader op til {{freeTierLimit}} brugere gratis pr. server. For at skalere uden afbrydelser og få tidlig adgang til vores nye PDF-tekstredigeringsværktøj anbefaler vi Stirling Server-planen – fuld redigering og ubegrænsede pladser for $99/server/md." +freeBody = "Vores Open-Core-licens tillader op til {{freeTierLimit}} brugere gratis pr. server. For at skalere uden afbrydelser anbefaler vi Stirling Server-planen – ubegrænsede pladser og SSO-understøttelse for $99/server/md." [onboarding.desktopInstall] title = "Download" @@ -5237,6 +5367,31 @@ error = "Kunne ikke opdatere brugerstatus" success = "Bruger slettet" error = "Kunne ikke slette bruger" +[workspace.people.changePassword] +action = "Skift adgangskode" +title = "Skift adgangskode" +subtitle = "Opdater adgangskoden for" +newPassword = "Ny adgangskode" +confirmPassword = "Bekræft adgangskode" +placeholder = "Indtast en ny adgangskode" +confirmPlaceholder = "Indtast den nye adgangskode igen" +passwordRequired = "Angiv en ny adgangskode" +passwordMismatch = "Adgangskoderne matcher ikke" +generateRandom = "Generér sikker adgangskode" +generatedPreview = "Genereret adgangskode:" +copyTooltip = "Kopiér til udklipsholder" +copiedToClipboard = "Adgangskode kopieret til udklipsholderen" +copyFailed = "Kunne ikke kopiere adgangskoden" +sendEmail = "Send en e-mail til brugeren om denne ændring" +includePassword = "Medtag den nye adgangskode i e-mailen" +forcePasswordChange = "Tving brugeren til at ændre adgangskode ved næste login" +emailUnavailable = "Denne brugers e-mail er ikke en gyldig e-mailadresse. Meddelelser er deaktiveret." +smtpDisabled = "E-mailmeddelelser kræver, at SMTP er aktiveret i indstillingerne." +notifyOnly = "Der sendes en e-mail uden adgangskoden, som informerer brugeren om, at en administrator har ændret den." +submit = "Opdater adgangskode" +success = "Adgangskoden blev opdateret" +error = "Kunne ikke opdatere adgangskoden" + [workspace.people.emailInvite] tab = "E-mailinvitation" description = "Skriv eller indsæt e-mails nedenfor, adskilt af kommaer. Brugere modtager loginoplysninger via e-mail." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Mindst én e-mailadresse er påkrævet" submit = "Send invitationer" success = "Bruger(e) inviteret" -partialSuccess = "Nogle invitationer mislykkedes" +partialFailure = "Nogle invitationer mislykkedes" allFailed = "Kunne ikke invitere brugere" error = "Kunne ikke sende invitationer" @@ -5288,8 +5443,8 @@ emailDisabled = "E-mailinvitationer kræver SMTP-konfiguration og mail.enableInv [workspace.people.license] users = "brugere" availableSlots = "Tilgængelige pladser" -grandfathered = "Grandfathered" -grandfatheredShort = "{{count}} grandfathered" +grandfathered = "På gamle vilkår" +grandfatheredShort = "{{count}} på gamle vilkår" fromLicense = "fra licens" slotsAvailable = "{{count}} ledig(e) brugerplads(er)" noSlotsAvailable = "Ingen pladser tilgængelige" @@ -5709,7 +5864,7 @@ title = "Diagram over endpoint-brug" [usage.table] title = "Detaljeret statistik" -endpoint = "Endpoint" +endpoint = "Slutpunkt" visits = "Besøg" percentage = "Procent" noData = "Ingen data tilgængelige" @@ -5752,7 +5907,7 @@ label = "Vælg server" description = "Selvhostet server" [setup.step3] -label = "Login" +label = "Log ind" description = "Indtast loginoplysninger" [setup.mode.saas] @@ -5770,6 +5925,7 @@ subtitle = "Log ind med din Stirling-konto" [setup.selfhosted] title = "Log ind på server" subtitle = "Indtast dine server-loginoplysninger" +link = "eller opret forbindelse til en selvhostet konto" [setup.server] title = "Forbind til server" @@ -5788,6 +5944,14 @@ description = "Indtast den fulde URL til din selvhostede Stirling PDF-server" emptyUrl = "Indtast en server-URL" unreachable = "Kunne ikke forbinde til server" testFailed = "Forbindelsestest mislykkedes" +configFetch = "Kunne ikke hente serverkonfiguration. Kontrollér URL'en, og prøv igen." + +[setup.server.error.securityDisabled] +title = "Login ikke aktiveret" +body = "Denne server har ikke login aktiveret. For at oprette forbindelse til denne server skal du aktivere godkendelse:" +step1 = "Sæt DOCKER_ENABLE_SECURITY=true i dit miljø" +step2 = "Eller sæt security.enableLogin=true i settings.yml" +step3 = "Genstart serveren" [setup.login] title = "Log ind" @@ -5797,6 +5961,13 @@ submit = "Log ind" signInWith = "Log ind med" oauthPending = "Åbner browser for godkendelse..." orContinueWith = "Eller fortsæt med email" +serverRequirement = "Bemærk: Serveren skal have login aktiveret." +showInstructions = "Hvordan aktiveres det?" +hideInstructions = "Skjul instruktioner" +instructions = "Sådan aktiverer du login på din Stirling PDF-server:" +instructionsEnvVar = "Sæt miljøvariablen:" +instructionsOrYml = "Eller i settings.yml:" +instructionsRestart = "Genstart derefter serveren, så ændringerne træder i kraft." [setup.login.username] label = "Brugernavn" @@ -5853,6 +6024,7 @@ earlyAccess = "Tidlig adgang" reset = "Nulstil ændringer" downloadJson = "Download JSON" generatePdf = "Generer PDF" +saveChanges = "Gem ændringer" [pdfTextEditor.options.autoScaleText] title = "Autoskalér tekst, så den passer i bokse" @@ -5890,6 +6062,8 @@ alpha = "Denne alpha-fremviser er stadig under udvikling—visse skrifttyper, fa [pdfTextEditor.empty] title = "Intet dokument indlæst" subtitle = "Indlæs en PDF- eller JSON-fil for at begynde at redigere tekstindhold." +dropzone = "Træk og slip en PDF- eller JSON-fil her, eller klik for at gennemse" +dropzoneWithFiles = "Vælg en fil fra fanen Filer, eller træk og slip en PDF- eller JSON-fil her, eller klik for at gennemse" [pdfTextEditor.welcomeBanner] title = "Velkommen til PDF-teksteditor (Early Access)" diff --git a/frontend/public/locales/de-DE/translation.toml b/frontend/public/locales/de-DE/translation.toml index c8c8e6f71..d48c424f4 100644 --- a/frontend/public/locales/de-DE/translation.toml +++ b/frontend/public/locales/de-DE/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Aus Favoriten entfernen" fullscreen = "In den Vollbildmodus wechseln" sidebar = "In den Seitenleistenmodus wechseln" +[backendStartup] +notFoundTitle = "Backend nicht gefunden" +retry = "Erneut versuchen" +unreachable = "Die Anwendung kann derzeit keine Verbindung zum Backend herstellen. Überprüfen Sie den Backend-Status und die Netzwerkverbindung und versuchen Sie es dann erneut." + [zipWarning] title = "Große ZIP-Datei" message = "Dieses ZIP enthält {{count}} Dateien. Trotzdem extrahieren?" @@ -347,7 +352,7 @@ teams = "Teams" title = "Konfiguration" systemSettings = "Systemeinstellungen" features = "Funktionen" -endpoints = "Endpoints" +endpoints = "Endpunkte" database = "Datenbank" advanced = "Erweitert" @@ -383,7 +388,7 @@ logout = "Abmelden" [settings.connection.mode] saas = "Stirling Cloud" -selfhosted = "Self-Hosted" +selfhosted = "Selbst gehostet" [settings.general] title = "Allgemein" @@ -612,7 +617,7 @@ desc = "Anzeigen, Kommentieren, Text oder Bilder hinzufügen" brandAlt = "Stirling PDF-Logo" openFiles = "Dateien öffnen" swipeHint = "Zum Wechseln der Ansicht nach links oder rechts wischen" -tools = "Tools" +tools = "Werkzeuge" toolsSlide = "Bereich für Toolauswahl" viewSwitcher = "Ansicht des Arbeitsbereichs wechseln" workbenchSlide = "Arbeitsbereichs-Panel" @@ -912,9 +917,12 @@ desc = "Mehrstufige Arbeitsabläufe durch Verkettung von PDF-Aktionen erstellen. desc = "Ein PDF über ein anderes legen" title = "PDFs überlagern" +[home.pdfTextEditor] +title = "PDF-Texteditor" +desc = "Vorhandenen Text und Bilder in PDFs bearbeiten" [home.addText] -tags = "text,annotation,label" +tags = "text,anmerkung,beschriftung" title = "Text hinzufügen" desc = "Beliebigen Text überall in Ihrem PDF hinzufügen" @@ -1213,7 +1221,7 @@ pdfaDigitalSignatureWarning = "Das PDF enthält eine digitale Signatur. Sie wird fileFormat = "Dateiformat" wordDoc = "Word-Dokument" wordDocExt = "Word-Dokument (.docx)" -odtExt = "OpenDocument Text (.odt)" +odtExt = "OpenDocument-Text (.odt)" pptExt = "PowerPoint (.pptx)" odpExt = "OpenDocument Präsentation (.odp)" txtExt = "Einfacher Text (.txt)" @@ -2259,12 +2267,20 @@ defaultCanvasLabel = "Gezeichnete Unterschrift" defaultImageLabel = "Hochgeladene Unterschrift" defaultTextLabel = "Getippte Unterschrift" saveButton = "Unterschrift speichern" +savePersonal = "Persönlich speichern" +saveShared = "Geteilt speichern" saveUnavailable = "Erstellen Sie zuerst eine Unterschrift, um sie zu speichern." noChanges = "Die aktuelle Unterschrift ist bereits gespeichert." +tempStorageTitle = "Temporärer Browser-Speicher" +tempStorageDescription = "Signaturen werden nur in Ihrem Browser gespeichert. Sie gehen verloren, wenn Sie Browserdaten löschen oder den Browser wechseln." +personalHeading = "Persönliche Signaturen" +sharedHeading = "Geteilte Signaturen" +personalDescription = "Nur Sie können diese Signaturen sehen." +sharedDescription = "Alle Benutzer können diese Signaturen sehen und verwenden." [sign.saved.type] canvas = "Zeichnung" -image = "Upload" +image = "Hochladen" text = "Text" [sign.saved.status] @@ -3020,6 +3036,91 @@ title = "Alle Informationen anzeigen" header = "Alle Informationen anzeigen" submit = "Informationen anzeigen" downloadJson = "Als JSON herunterladen" +processing = "Informationen werden extrahiert..." +results = "Ergebnisse" +noResults = "Führen Sie das Tool aus, um einen Bericht zu erstellen." +downloads = "Downloads" +noneDetected = "Keine erkannt" +indexTitle = "Index" + +[getPdfInfo.report] +entryLabel = "Vollständige Informationsübersicht" +shortTitle = "PDF-Informationen" + +[getPdfInfo.sections] +metadata = "Metadaten" +formFields = "Formularfelder" +basicInfo = "Grundlegende Informationen" +documentInfo = "Dokumentinformationen" +compliance = "Compliance" +encryption = "Verschlüsselung" +permissions = "Berechtigungen" +other = "Sonstiges" +perPageInfo = "Informationen pro Seite" +tableOfContents = "Inhaltsverzeichnis" + +[getPdfInfo.other] +attachments = "Anhänge" +embeddedFiles = "Eingebettete Dateien" +javaScript = "JavaScript" +layers = "Ebenen" +structureTree = "Strukturbaum" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Größe" +annotations = "Anmerkungen" +images = "Bilder" +links = "Links" +fonts = "Schriftarten" +xobjects = "XObject-Anzahl" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Seiten" +fileSize = "Dateigröße" +pdfVersion = "PDF-Version" +language = "Sprache" +title = "PDF-Zusammenfassung" +author = "Autor" +created = "Erstellt" +modified = "Geändert" +permsAll = "Alle Berechtigungen erlaubt" +permsRestricted = "{{count}} Einschränkungen" +permsMixed = "Einige Berechtigungen eingeschränkt" +hasCompliance = "Entspricht Compliance-Standards" +noCompliance = "Keine Compliance-Standards" +basic = "Grundlegende Informationen" +documentInfo = "Dokumentinformationen" +securityTitle = "Sicherheitsstatus" +technical = "Technisch" +overviewTitle = "PDF-Übersicht" + +[getPdfInfo.summary.security] +encrypted = "Verschlüsseltes PDF - Passwortschutz vorhanden" +unencrypted = "Unverschlüsseltes PDF - Kein Passwortschutz" + +[getPdfInfo.summary.tech] +images = "Bilder" +fonts = "Schriftarten" +formFields = "Formularfelder" +embeddedFiles = "Eingebettete Dateien" +javaScript = "JavaScript" +layers = "Ebenen" +bookmarks = "Lesezeichen" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "ein unbenanntes Dokument" +unknown = "Unbekannter Autor" +text = "Dies ist ein {{pages}}-seitiges PDF mit dem Titel {{title}}, erstellt von {{author}} (PDF-Version {{version}})." + +[getPdfInfo.error] +partial = "Einige Dateien konnten nicht verarbeitet werden." +unexpected = "Unerwarteter Fehler während der Extraktion." + +[getPdfInfo.status] +complete = "Extraktion abgeschlossen" [extractPage] tags = "extrahieren,seite" @@ -3438,6 +3539,9 @@ signinTitle = "Bitte melden Sie sich an." ssoSignIn = "Anmeldung per Single Sign-On" oAuth2AutoCreateDisabled = "OAUTH2 Benutzer automatisch erstellen deaktiviert" oAuth2AdminBlockedUser = "Die Registrierung bzw. das anmelden von nicht registrierten Benutzern ist derzeit gesperrt. Bitte wenden Sie sich an den Administrator." +oAuth2RequiresLicense = "OAuth/SSO-Anmeldung erfordert eine kostenpflichtige Lizenz (Server oder Enterprise). Bitte wenden Sie sich an den Administrator, um Ihren Plan zu aktualisieren." +saml2RequiresLicense = "SAML-Anmeldung erfordert eine kostenpflichtige Lizenz (Server oder Enterprise). Bitte wenden Sie sich an den Administrator, um Ihren Plan zu aktualisieren." +maxUsersReached = "Die maximale Benutzeranzahl für Ihre aktuelle Lizenz wurde erreicht. Bitte wenden Sie sich an den Administrator, um Ihren Plan zu aktualisieren oder weitere Benutzerplätze hinzuzufügen." oauth2RequestNotFound = "Autorisierungsanfrage nicht gefunden" oauth2InvalidUserInfoResponse = "Ungültige Benutzerinformationsantwort" oauth2invalidRequest = "ungültige Anfrage" @@ -3846,14 +3950,17 @@ fitToWidth = "An Breite anpassen" actualSize = "Originalgröße" [viewer] +cannotPreviewFile = "Datei kann nicht in der Vorschau angezeigt werden" +dualPageView = "Doppelseitenansicht" firstPage = "Erste Seite" lastPage = "Letzte Seite" -previousPage = "Vorherige Seite" nextPage = "Nächste Seite" +onlyPdfSupported = "Der Viewer unterstützt nur PDF-Dateien. Diese Datei scheint ein anderes Format zu haben." +previousPage = "Vorherige Seite" +singlePageView = "Einzelseitenansicht" +unknownFile = "Unbekannte Datei" zoomIn = "Vergrößern" zoomOut = "Verkleinern" -singlePageView = "Einzelseitenansicht" -dualPageView = "Doppelseitenansicht" [rightRail] closeSelected = "Ausgewählte Dateien schließen" @@ -3877,6 +3984,7 @@ toggleSidebar = "Seitenleiste umschalten" exportSelected = "Ausgewählte Seiten exportieren" toggleAnnotations = "Anmerkungen ein-/ausblenden" annotationMode = "Anmerkungsmodus umschalten" +print = "PDF drucken" draw = "Zeichnen" save = "Speichern" saveChanges = "Änderungen speichern" @@ -3928,7 +4036,7 @@ account = "Konto" config = "Konfig" settings = "Optionen" adminSettings = "Admin Optionen" -allTools = "Tools" +allTools = "Werkzeuge" reader = "Reader" [quickAccess.helpMenu] @@ -4494,6 +4602,7 @@ description = "URL oder Dateiname zum Impressum (in einigen Rechtsordnungen erfo title = "Premium & Enterprise" description = "Ihren Premium- oder Enterprise-Lizenzschlüssel konfigurieren." license = "Lizenzkonfiguration" +noInput = "Bitte geben Sie einen Lizenzschlüssel oder eine Datei an" [admin.settings.premium.licenseKey] toggle = "Lizenzschlüssel oder Zertifikatsdatei vorhanden?" @@ -4511,6 +4620,25 @@ line1 = "Das Überschreiben Ihres aktuellen Lizenzschlüssels kann nicht rückg line2 = "Ihre vorherige Lizenz geht dauerhaft verloren, sofern Sie sie nicht anderweitig gesichert haben." line3 = "Wichtig: Halten Sie Lizenzschlüssel privat und sicher. Geben Sie sie niemals öffentlich weiter." +[admin.settings.premium.inputMethod] +text = "Lizenzschlüssel" +file = "Zertifikatsdatei" + +[admin.settings.premium.file] +label = "Lizenz-Zertifikatsdatei" +description = "Laden Sie Ihre .lic- oder .cert-Lizenzdatei aus Offline-Käufen hoch" +choose = "Lizenzdatei auswählen" +selected = "Ausgewählt: {{filename}} ({{size}})" +successMessage = "Lizenzdatei erfolgreich hochgeladen und aktiviert. Kein Neustart erforderlich." + +[admin.settings.premium.currentLicense] +title = "Aktive Lizenz" +file = "Quelle: Lizenzdatei ({{path}})" +key = "Quelle: Lizenzschlüssel" +type = "Typ: {{type}}" +noInput = "Bitte geben Sie einen Lizenzschlüssel an oder laden Sie eine Zertifikatdatei hoch" +success = "Erfolg" + [admin.settings.premium.enabled] label = "Premium-Funktionen aktivieren" description = "Lizenzschlüssel-Prüfungen für Pro-/Enterprise-Funktionen aktivieren" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} ausgewählt" download = "Herunterladen" delete = "Löschen" unsupported = "Nicht unterstützt" +active = "Aktiv" addToUpload = "Zum Upload hinzufügen" +closeFile = "Datei schließen" deleteAll = "Alle löschen" loadingFiles = "Dateien werden geladen..." noFiles = "Keine Dateien verfügbar" @@ -5132,7 +5262,7 @@ upgrade = "Jetzt upgraden →" freeTitle = "Server-Lizenz" overLimitTitle = "Server-Lizenz erforderlich" overLimitBody = "Unsere Lizenz erlaubt bis zu {{freeTierLimit}} Nutzer pro Server kostenlos. Sie haben {{overLimitUserCopy}} Stirling-Nutzer. Um ohne Unterbrechung fortzufahren, upgraden Sie auf den Stirling-Server-Plan – unbegrenzte Plätze, PDF-Textbearbeitung und volle Admin-Kontrolle für $99/Server/Monat." -freeBody = "Unsere Open-Core-Lizenz erlaubt bis zu {{freeTierLimit}} Nutzer pro Server kostenlos. Für unterbrechungsfreies Skalieren und frühen Zugriff auf unser neues PDF-Textbearbeitungs-Tool empfehlen wir den Stirling-Server-Plan – volle Bearbeitung und unbegrenzte Plätze für $99/Server/Monat." +freeBody = "Unsere Open-Core-Lizenz erlaubt bis zu {{freeTierLimit}} Nutzern pro Server kostenlos. Um unterbrechungsfrei zu skalieren, empfehlen wir den Stirling Server-Plan - unbegrenzte Plätze und SSO-Unterstützung für $99/Server/Monat." [onboarding.desktopInstall] title = "Download" @@ -5237,6 +5367,31 @@ error = "Benutzerstatus konnte nicht aktualisiert werden" success = "Benutzer erfolgreich gelöscht" error = "Benutzer konnte nicht gelöscht werden" +[workspace.people.changePassword] +action = "Passwort ändern" +title = "Passwort ändern" +subtitle = "Passwort aktualisieren für" +newPassword = "Neues Passwort" +confirmPassword = "Passwort bestätigen" +placeholder = "Neues Passwort eingeben" +confirmPlaceholder = "Neues Passwort erneut eingeben" +passwordRequired = "Bitte geben Sie ein neues Passwort ein" +passwordMismatch = "Passwörter stimmen nicht überein" +generateRandom = "Sicheres Passwort generieren" +generatedPreview = "Generiertes Passwort:" +copyTooltip = "In Zwischenablage kopieren" +copiedToClipboard = "Passwort in die Zwischenablage kopiert" +copyFailed = "Kopieren des Passworts fehlgeschlagen" +sendEmail = "Den Benutzer per E-Mail über diese Änderung informieren" +includePassword = "Neues Passwort in die E-Mail aufnehmen" +forcePasswordChange = "Benutzer zwingen, das Passwort bei der nächsten Anmeldung zu ändern" +emailUnavailable = "Die E-Mail-Adresse dieses Benutzers ist keine gültige E-Mail-Adresse. Benachrichtigungen sind deaktiviert." +smtpDisabled = "E-Mail-Benachrichtigungen erfordern, dass SMTP in den Einstellungen aktiviert ist." +notifyOnly = "Es wird eine E-Mail ohne das Passwort gesendet, die den Benutzer darüber informiert, dass ein Admin es geändert hat." +submit = "Passwort aktualisieren" +success = "Passwort erfolgreich aktualisiert" +error = "Aktualisieren des Passworts fehlgeschlagen" + [workspace.people.emailInvite] tab = "E-Mail-Einladung" description = "Geben Sie unten E-Mails ein oder fügen Sie sie ein, getrennt durch Kommas. Benutzer erhalten Anmeldedaten per E-Mail." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Mindestens eine E-Mail-Adresse ist erforderlich" submit = "Einladungen senden" success = "Benutzer erfolgreich eingeladen" -partialSuccess = "Einige Einladungen sind fehlgeschlagen" +partialFailure = "Einige Einladungen sind fehlgeschlagen" allFailed = "Benutzer konnten nicht eingeladen werden" error = "Einladungen konnten nicht gesendet werden" @@ -5752,7 +5907,7 @@ label = "Server auswählen" description = "Self-Hosted-Server" [setup.step3] -label = "Login" +label = "Anmeldung" description = "Anmeldedaten eingeben" [setup.mode.saas] @@ -5770,6 +5925,7 @@ subtitle = "Mit Ihrem Stirling-Konto anmelden" [setup.selfhosted] title = "Am Server anmelden" subtitle = "Geben Sie Ihre Server-Anmeldedaten ein" +link = "oder mit einem selbstgehosteten Konto verbinden" [setup.server] title = "Mit Server verbinden" @@ -5788,15 +5944,30 @@ description = "Geben Sie die vollständige URL Ihres selbst gehosteten Stirling emptyUrl = "Bitte eine Server-URL eingeben" unreachable = "Verbindung zum Server konnte nicht hergestellt werden" testFailed = "Verbindungstest fehlgeschlagen" +configFetch = "Serverkonfiguration konnte nicht abgerufen werden. Bitte überprüfen Sie die URL und versuchen Sie es erneut." + +[setup.server.error.securityDisabled] +title = "Anmeldung nicht aktiviert" +body = "Auf diesem Server ist die Anmeldung nicht aktiviert. Um eine Verbindung zu diesem Server herzustellen, müssen Sie die Authentifizierung aktivieren:" +step1 = "Setzen Sie DOCKER_ENABLE_SECURITY=true in Ihrer Umgebung" +step2 = "Oder setzen Sie security.enableLogin=true in der settings.yml" +step3 = "Starten Sie den Server neu" [setup.login] title = "Anmelden" subtitle = "Geben Sie Ihre Anmeldedaten ein, um fortzufahren" connectingTo = "Verbinden mit:" -submit = "Login" +submit = "Anmelden" signInWith = "Anmelden mit" oauthPending = "Browser zur Authentifizierung wird geöffnet..." orContinueWith = "Oder mit E-Mail fortfahren" +serverRequirement = "Hinweis: Auf dem Server muss die Anmeldung aktiviert sein." +showInstructions = "Wie aktivieren?" +hideInstructions = "Anleitung ausblenden" +instructions = "So aktivieren Sie die Anmeldung auf Ihrem Stirling PDF-Server:" +instructionsEnvVar = "Setzen Sie die Umgebungsvariable:" +instructionsOrYml = "Oder in der settings.yml:" +instructionsRestart = "Starten Sie anschließend Ihren Server neu, damit die Änderungen wirksam werden." [setup.login.username] label = "Benutzername" @@ -5847,12 +6018,13 @@ singleLine = "Einzeilig" [pdfTextEditor.badges] unsaved = "Bearbeitet" modified = "Bearbeitet" -earlyAccess = "Early Access" +earlyAccess = "Früher Zugriff" [pdfTextEditor.actions] reset = "Änderungen zurücksetzen" downloadJson = "JSON herunterladen" generatePdf = "PDF generieren" +saveChanges = "Änderungen speichern" [pdfTextEditor.options.autoScaleText] title = "Text automatisch in Rahmen einpassen" @@ -5890,6 +6062,8 @@ alpha = "Dieser Alpha-Viewer entwickelt sich noch weiter – bestimmte Schriften [pdfTextEditor.empty] title = "Kein Dokument geladen" subtitle = "Laden Sie eine PDF- oder JSON-Datei, um mit der Textbearbeitung zu beginnen." +dropzone = "Ziehen Sie eine PDF- oder JSON-Datei hierher, oder klicken Sie zum Durchsuchen" +dropzoneWithFiles = "Wählen Sie eine Datei auf der Registerkarte Dateien aus oder ziehen Sie eine PDF- oder JSON-Datei hierher, oder klicken Sie zum Durchsuchen" [pdfTextEditor.welcomeBanner] title = "Willkommen beim PDF-Texteditor (Early Access)" @@ -5957,8 +6131,8 @@ tags = "text,anmerkung,beschriftung" applySignatures = "Text anwenden" [addText.text] -name = "Textinhalt" -placeholder = "Geben Sie den hinzuzufügenden Text ein" +name = "Text" +placeholder = "Text eingeben" fontLabel = "Schriftart" fontSizeLabel = "Schriftgröße" fontSizePlaceholder = "Schriftgröße eingeben oder wählen (8-200)" diff --git a/frontend/public/locales/el-GR/translation.toml b/frontend/public/locales/el-GR/translation.toml index 265646627..5af71838c 100644 --- a/frontend/public/locales/el-GR/translation.toml +++ b/frontend/public/locales/el-GR/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Αφαίρεση από τα Αγαπημένα" fullscreen = "Μετάβαση σε λειτουργία πλήρους οθόνης" sidebar = "Μετάβαση σε λειτουργία πλευρικής γραμμής" +[backendStartup] +notFoundTitle = "Το backend δεν βρέθηκε" +retry = "Επανάληψη" +unreachable = "Η εφαρμογή δεν μπορεί προς το παρόν να συνδεθεί με το backend. Ελέγξτε την κατάσταση του backend και τη συνδεσιμότητα δικτύου, μετά δοκιμάστε ξανά." + [zipWarning] title = "Μεγάλο αρχείο ZIP" message = "Αυτό το ZIP περιέχει {{count}} αρχεία. Να γίνει αποσυμπίεση ούτως ή άλλως;" @@ -287,7 +292,7 @@ help = "Βοήθεια Pipeline" scanHelp = "Βοήθεια σάρωσης φακέλων" deletePrompt = "Είστε βέβαιοι ότι θέλετε να διαγράψετε το pipeline;" tags = "αυτοματοποίηση,ακολουθία,προγραμματισμένο,επεξεργασία-παρτίδας" -title = "Pipeline" +title = "Ροή" [pipelineOptions] header = "Διαμόρφωση Pipeline" @@ -296,7 +301,7 @@ saveSettings = "Αποθήκευση ρυθμίσεων λειτουργίας" pipelineNamePrompt = "Εισάγετε όνομα pipeline εδώ" selectOperation = "Επιλογή λειτουργίας" addOperationButton = "Προσθήκη λειτουργίας" -pipelineHeader = "Pipeline:" +pipelineHeader = "Ροή:" saveButton = "Λήψη" validateButton = "Επικύρωση" @@ -347,7 +352,7 @@ teams = "Ομάδες" title = "Διαμόρφωση" systemSettings = "Ρυθμίσεις συστήματος" features = "Δυνατότητες" -endpoints = "Endpoints" +endpoints = "Σημεία τερματισμού" database = "Βάση δεδομένων" advanced = "Προχωρημένα" @@ -369,7 +374,7 @@ privacy = "Απόρρητο" [settings.developer] title = "Προγραμματιστής" -apiKeys = "API Keys" +apiKeys = "Κλειδιά API" [settings.tooltips] enableLoginFirst = "Ενεργοποιήστε πρώτα τη λειτουργία σύνδεσης" @@ -383,7 +388,7 @@ logout = "Αποσύνδεση" [settings.connection.mode] saas = "Stirling Cloud" -selfhosted = "Self-Hosted" +selfhosted = "Αυτο-φιλοξενούμενο" [settings.general] title = "Γενικά" @@ -912,6 +917,9 @@ desc = "Δημιουργήστε ροές πολλών βημάτων συνδέ desc = "Επικάλυψη PDF πάνω σε άλλο PDF" title = "Επικάλυψη PDF" +[home.pdfTextEditor] +title = "Επεξεργαστής κειμένου PDF" +desc = "Επεξεργαστείτε υπάρχον κείμενο και εικόνες μέσα σε αρχεία PDF" [home.addText] tags = "κείμενο,σχολιασμός,ετικέτα" @@ -1173,7 +1181,7 @@ selectFilesPlaceholder = "Επιλέξτε αρχεία στην κύρια πρ settings = "Ρυθμίσεις" conversionCompleted = "Η μετατροπή ολοκληρώθηκε" results = "Αποτελέσματα" -defaultFilename = "converted_file" +defaultFilename = "μετατραπμένο_αρχείο" conversionResults = "Αποτελέσματα μετατροπής" convertFrom = "Μετατροπή από" convertTo = "Μετατροπή σε" @@ -1360,7 +1368,7 @@ title = "Προσθήκη υδατογραφήματος" desc = "Προσθέστε υδατογραφήματα κειμένου ή εικόνας σε αρχεία PDF" completed = "Το υδατογράφημα προστέθηκε" submit = "Προσθήκη υδατογραφήματος" -filenamePrefix = "watermarked" +filenamePrefix = "υδατογραφημένο" [watermark.error] failed = "Παρουσιάστηκε σφάλμα κατά την προσθήκη υδατογραφήματος στο PDF." @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Σχεδιασμένη υπογραφή" defaultImageLabel = "Ανεβασμένη υπογραφή" defaultTextLabel = "Πληκτρολογημένη υπογραφή" saveButton = "Αποθήκευση υπογραφής" +savePersonal = "Αποθήκευση ως Προσωπική" +saveShared = "Αποθήκευση ως Κοινόχρηστη" saveUnavailable = "Δημιουργήστε πρώτα μια υπογραφή για να την αποθηκεύσετε." noChanges = "Η τρέχουσα υπογραφή είναι ήδη αποθηκευμένη." +tempStorageTitle = "Προσωρινή αποθήκευση στον περιηγητή" +tempStorageDescription = "Οι υπογραφές αποθηκεύονται μόνο στον περιηγητή σας. Θα χαθούν αν καθαρίσετε τα δεδομένα του περιηγητή ή αλλάξετε περιηγητή." +personalHeading = "Προσωπικές υπογραφές" +sharedHeading = "Κοινόχρηστες υπογραφές" +personalDescription = "Μόνο εσείς μπορείτε να δείτε αυτές τις υπογραφές." +sharedDescription = "Όλοι οι χρήστες μπορούν να βλέπουν και να χρησιμοποιούν αυτές τις υπογραφές." [sign.saved.type] canvas = "Σχέδιο" @@ -2701,7 +2717,7 @@ header = "Αφαίρεση της ψηφιακής υπογραφής από τ selectPDF = "Επιλέξτε ένα αρχείο PDF:" submit = "Αφαίρεση υπογραφής" description = "Αυτό το εργαλείο θα αφαιρέσει τις υπογραφές ψηφιακού πιστοποιητικού από το PDF σας." -filenamePrefix = "unsigned" +filenamePrefix = "ανυπόγραφο" [removeCertSign.files] placeholder = "Επιλέξτε ένα αρχείο PDF στην κύρια προβολή για να ξεκινήσετε" @@ -3020,6 +3036,91 @@ title = "Λήψη πληροφοριών PDF" header = "Λήψη πληροφοριών PDF" submit = "Λήψη πληροφοριών" downloadJson = "Λήψη JSON" +processing = "Εξαγωγή πληροφοριών..." +results = "Αποτελέσματα" +noResults = "Εκτελέστε το εργαλείο για να δημιουργήσετε αναφορά." +downloads = "Λήψεις" +noneDetected = "Δεν εντοπίστηκε κανένα" +indexTitle = "Ευρετήριο" + +[getPdfInfo.report] +entryLabel = "Πλήρης σύνοψη πληροφοριών" +shortTitle = "Πληροφορίες PDF" + +[getPdfInfo.sections] +metadata = "Μεταδεδομένα" +formFields = "Πεδία φόρμας" +basicInfo = "Βασικές πληροφορίες" +documentInfo = "Πληροφορίες εγγράφου" +compliance = "Συμμόρφωση" +encryption = "Κρυπτογράφηση" +permissions = "Δικαιώματα" +other = "Άλλα" +perPageInfo = "Πληροφορίες ανά σελίδα" +tableOfContents = "Πίνακας περιεχομένων" + +[getPdfInfo.other] +attachments = "Συνημμένα" +embeddedFiles = "Ενσωματωμένα αρχεία" +javaScript = "JavaScript" +layers = "Επίπεδα" +structureTree = "Δέντρο δομής" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Μέγεθος" +annotations = "Επισημειώσεις" +images = "Εικόνες" +links = "Σύνδεσμοι" +fonts = "Γραμματοσειρές" +xobjects = "Πλήθος XObject" +multimedia = "Πολυμέσα" + +[getPdfInfo.summary] +pages = "Σελίδες" +fileSize = "Μέγεθος αρχείου" +pdfVersion = "Έκδοση PDF" +language = "Γλώσσα" +title = "Σύνοψη PDF" +author = "Συγγραφέας" +created = "Δημιουργήθηκε" +modified = "Τροποποιήθηκε" +permsAll = "Όλα τα δικαιώματα επιτρέπονται" +permsRestricted = "{{count}} περιορισμοί" +permsMixed = "Ορισμένα δικαιώματα είναι περιορισμένα" +hasCompliance = "Διαθέτει πρότυπα συμμόρφωσης" +noCompliance = "Χωρίς πρότυπα συμμόρφωσης" +basic = "Βασικές πληροφορίες" +documentInfo = "Πληροφορίες εγγράφου" +securityTitle = "Κατάσταση ασφάλειας" +technical = "Τεχνικά" +overviewTitle = "Επισκόπηση PDF" + +[getPdfInfo.summary.security] +encrypted = "Κρυπτογραφημένο PDF - Υπάρχει προστασία με κωδικό πρόσβασης" +unencrypted = "Μη κρυπτογραφημένο PDF - Χωρίς προστασία με κωδικό πρόσβασης" + +[getPdfInfo.summary.tech] +images = "Εικόνες" +fonts = "Γραμματοσειρές" +formFields = "Πεδία φόρμας" +embeddedFiles = "Ενσωματωμένα αρχεία" +javaScript = "JavaScript" +layers = "Επίπεδα" +bookmarks = "Σελιδοδείκτες" +multimedia = "Πολυμέσα" + +[getPdfInfo.summary.overview] +untitled = "ένα έγγραφο χωρίς τίτλο" +unknown = "Άγνωστος συγγραφέας" +text = "Πρόκειται για ένα PDF {{pages}} σελίδων με τίτλο {{title}} που δημιουργήθηκε από τον/την {{author}} (έκδοση PDF {{version}})." + +[getPdfInfo.error] +partial = "Δεν ήταν δυνατή η επεξεργασία ορισμένων αρχείων." +unexpected = "Μη αναμενόμενο σφάλμα κατά την εξαγωγή." + +[getPdfInfo.status] +complete = "Η εξαγωγή ολοκληρώθηκε" [extractPage] tags = "εξαγωγή" @@ -3438,6 +3539,9 @@ signinTitle = "Παρακαλώ συνδεθείτε" ssoSignIn = "Σύνδεση μέσω Single Sign-on" oAuth2AutoCreateDisabled = "Η αυτόματη δημιουργία χρήστη OAUTH2 είναι απενεργοποιημένη" oAuth2AdminBlockedUser = "Η εγγραφή ή σύνδεση μη εγγεγραμμένων χρηστών είναι προς το παρόν αποκλεισμένη. Παρακαλώ επικοινωνήστε με τον διαχειριστή." +oAuth2RequiresLicense = "Η σύνδεση μέσω OAuth/SSO απαιτεί επί πληρωμή άδεια (Server ή Enterprise). Παρακαλούμε επικοινωνήστε με τον διαχειριστή για να αναβαθμίσετε το πλάνο σας." +saml2RequiresLicense = "Η σύνδεση μέσω SAML απαιτεί επί πληρωμή άδεια (Server ή Enterprise). Παρακαλούμε επικοινωνήστε με τον διαχειριστή για να αναβαθμίσετε το πλάνο σας." +maxUsersReached = "Έχει επιτευχθεί ο μέγιστος αριθμός χρηστών για την τρέχουσα άδειά σας. Παρακαλούμε επικοινωνήστε με τον διαχειριστή για να αναβαθμίσετε το πλάνο σας ή να προσθέσετε περισσότερες θέσεις." oauth2RequestNotFound = "Το αίτημα εξουσιοδότησης δεν βρέθηκε" oauth2InvalidUserInfoResponse = "Μη έγκυρη απόκριση πληροφοριών χρήστη" oauth2invalidRequest = "Μη έγκυρο αίτημα" @@ -3533,7 +3637,7 @@ title = "PDF σε μία σελίδα" header = "PDF σε μία σελίδα" submit = "Μετατροπή σε μία σελίδα" description = "Αυτό το εργαλείο θα συγχωνεύσει όλες τις σελίδες του PDF σας σε μία μεγάλη ενιαία σελίδα. Το πλάτος θα παραμείνει ίδιο με των αρχικών σελίδων, αλλά το ύψος θα είναι το άθροισμα όλων των υψών." -filenamePrefix = "single_page" +filenamePrefix = "μονοσέλιδο" [pdfToSinglePage.files] placeholder = "Επιλέξτε ένα αρχείο PDF στην κύρια προβολή για να ξεκινήσετε" @@ -3771,7 +3875,7 @@ version = "Τρέχουσα έκδοση" title = "Τεκμηρίωση API" header = "Τεκμηρίωση API" desc = "Προβάλετε και δοκιμάστε τα endpoints του Stirling PDF API" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,τεκμηρίωση,swagger,τελικά σημεία,ανάπτυξη" [cookieBanner.popUp] title = "Πώς χρησιμοποιούμε τα cookies" @@ -3846,14 +3950,17 @@ fitToWidth = "Προσαρμογή στο πλάτος" actualSize = "Πραγματικό μέγεθος" [viewer] +cannotPreviewFile = "Δεν είναι δυνατή η προεπισκόπηση του αρχείου" +dualPageView = "Προβολή διπλής σελίδας" firstPage = "Πρώτη σελίδα" lastPage = "Τελευταία σελίδα" -previousPage = "Προηγούμενη σελίδα" nextPage = "Επόμενη σελίδα" +onlyPdfSupported = "Ο προβολέας υποστηρίζει μόνο αρχεία PDF. Αυτό το αρχείο φαίνεται να είναι διαφορετικής μορφής." +previousPage = "Προηγούμενη σελίδα" +singlePageView = "Προβολή μίας σελίδας" +unknownFile = "Άγνωστο αρχείο" zoomIn = "Μεγέθυνση" zoomOut = "Σμίκρυνση" -singlePageView = "Προβολή μίας σελίδας" -dualPageView = "Προβολή διπλής σελίδας" [rightRail] closeSelected = "Κλείσιμο επιλεγμένων αρχείων" @@ -3877,6 +3984,7 @@ toggleSidebar = "Εναλλαγή πλευρικής γραμμής" exportSelected = "Εξαγωγή επιλεγμένων σελίδων" toggleAnnotations = "Εναλλαγή ορατότητας σχολιασμών" annotationMode = "Εναλλαγή λειτουργίας σχολιασμού" +print = "Εκτύπωση PDF" draw = "Σχεδίαση" save = "Αποθήκευση" saveChanges = "Αποθήκευση αλλαγών" @@ -4235,11 +4343,11 @@ label = "URL εκδότη" description = "Το URL εκδότη του παρόχου OAuth2" [admin.settings.connections.oauth2.clientId] -label = "Client ID" +label = "Αναγνωριστικό πελάτη (Client ID)" description = "Το Client ID OAuth2 από τον πάροχό σας" [admin.settings.connections.oauth2.clientSecret] -label = "Client Secret" +label = "Μυστικό πελάτη (Client Secret)" description = "Το Client Secret OAuth2 από τον πάροχό σας" [admin.settings.connections.oauth2.useAsUsername] @@ -4459,7 +4567,7 @@ label = "Ενεργοποίηση προσκλήσεων μέσω email" description = "Να επιτρέπεται στους διαχειριστές να προσκαλούν χρήστες μέσω email με αυτόματα παραγόμενους κωδικούς" [admin.settings.mail.frontendUrl] -label = "Frontend URL" +label = "URL front-end" description = "Βασικό URL για το frontend (π.χ. https://pdf.example.com). Χρησιμοποιείται για τη δημιουργία συνδέσμων πρόσκλησης στα email. Αφήστε κενό για χρήση του backend URL." [admin.settings.legal] @@ -4494,6 +4602,7 @@ description = "URL ή όνομα αρχείου για το impressum (απαι title = "Premium & Enterprise" description = "Ρυθμίστε το κλειδί άδειας premium ή enterprise." license = "Διαμόρφωση άδειας" +noInput = "Παρακαλώ δώστε ένα κλειδί άδειας ή αρχείο" [admin.settings.premium.licenseKey] toggle = "Έχετε κλειδί άδειας ή αρχείο πιστοποιητικού;" @@ -4511,6 +4620,25 @@ line1 = "Η αντικατάσταση του τρέχοντος κλειδιο line2 = "Η προηγούμενη άδεια θα χαθεί οριστικά εκτός αν την έχετε αποθηκεύσει αλλού." line3 = "Σημαντικό: Κρατήστε τα κλειδιά άδειας ιδιωτικά και ασφαλή. Μην τα κοινοποιείτε δημόσια." +[admin.settings.premium.inputMethod] +text = "Κλειδί άδειας" +file = "Αρχείο πιστοποιητικού" + +[admin.settings.premium.file] +label = "Αρχείο πιστοποιητικού άδειας" +description = "Μεταφορτώστε το αρχείο άδειας .lic ή .cert από αγορές εκτός σύνδεσης" +choose = "Επιλέξτε αρχείο άδειας" +selected = "Επιλεγμένο: {{filename}} ({{size}})" +successMessage = "Το αρχείο άδειας μεταφορτώθηκε και ενεργοποιήθηκε με επιτυχία. Δεν απαιτείται επανεκκίνηση." + +[admin.settings.premium.currentLicense] +title = "Ενεργή άδεια" +file = "Πηγή: Αρχείο άδειας ({{path}})" +key = "Πηγή: Κλειδί άδειας" +type = "Τύπος: {{type}}" +noInput = "Παρακαλώ δώστε ένα κλειδί άδειας ή μεταφορτώστε ένα αρχείο πιστοποιητικού" +success = "Επιτυχία" + [admin.settings.premium.enabled] label = "Ενεργοποίηση λειτουργιών premium" description = "Ενεργοποίηση ελέγχων κλειδιού άδειας για λειτουργίες pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} επιλεγμένα" download = "Λήψη" delete = "Διαγραφή" unsupported = "Μη υποστηριζόμενο" +active = "Ενεργό" addToUpload = "Προσθήκη στη μεταφόρτωση" +closeFile = "Κλείσιμο αρχείου" deleteAll = "Διαγραφή όλων" loadingFiles = "Φόρτωση αρχείων..." noFiles = "Δεν υπάρχουν διαθέσιμα αρχεία" @@ -5132,7 +5262,7 @@ upgrade = "Αναβάθμιση τώρα →" freeTitle = "Άδεια διακομιστή" overLimitTitle = "Απαιτείται άδεια διακομιστή" overLimitBody = "Η αδειοδότηση μας επιτρέπει έως {{freeTierLimit}} χρήστες δωρεάν ανά διακομιστή. Έχετε {{overLimitUserCopy}} χρήστες Stirling. Για να συνεχίσετε χωρίς διακοπές, αναβαθμίστε στο πλάνο Stirling Server - απεριόριστες θέσεις, επεξεργασία κειμένου PDF και πλήρης έλεγχος διαχειριστή για $99/server/μήνα." -freeBody = "Η αδειοδότηση Open-Core μας επιτρέπει έως {{freeTierLimit}} χρήστες δωρεάν ανά διακομιστή. Για απρόσκοπτη κλιμάκωση και έγκαιρη πρόσβαση στο νέο εργαλείο επεξεργασίας κειμένου PDF, προτείνουμε το πλάνο Stirling Server - πλήρης επεξεργασία και απεριόριστες θέσεις για $99/server/μήνα." +freeBody = "Οι άδειες χρήσης Open-Core επιτρέπουν έως και {{freeTierLimit}} χρήστες δωρεάν ανά διακομιστή. Για απρόσκοπτη κλιμάκωση, προτείνουμε το πλάνο Stirling Server - απεριόριστες θέσεις και υποστήριξη SSO με $99/διακομιστή/μήνα." [onboarding.desktopInstall] title = "Λήψη" @@ -5237,6 +5367,31 @@ error = "Αποτυχία ενημέρωσης κατάστασης χρήστη success = "Ο χρήστης διαγράφηκε με επιτυχία" error = "Αποτυχία διαγραφής χρήστη" +[workspace.people.changePassword] +action = "Αλλαγή κωδικού πρόσβασης" +title = "Αλλαγή κωδικού πρόσβασης" +subtitle = "Ενημέρωση κωδικού πρόσβασης για" +newPassword = "Νέος κωδικός πρόσβασης" +confirmPassword = "Επιβεβαίωση κωδικού πρόσβασης" +placeholder = "Εισαγάγετε νέο κωδικό πρόσβασης" +confirmPlaceholder = "Εισαγάγετε ξανά τον νέο κωδικό πρόσβασης" +passwordRequired = "Παρακαλούμε εισαγάγετε νέο κωδικό πρόσβασης" +passwordMismatch = "Οι κωδικοί πρόσβασης δεν ταιριάζουν" +generateRandom = "Δημιουργία ασφαλούς κωδικού πρόσβασης" +generatedPreview = "Δημιουργημένος κωδικός πρόσβασης:" +copyTooltip = "Αντιγραφή στο πρόχειρο" +copiedToClipboard = "Ο κωδικός πρόσβασης αντιγράφηκε στο πρόχειρο" +copyFailed = "Αποτυχία αντιγραφής κωδικού πρόσβασης" +sendEmail = "Αποστολή email στον χρήστη για αυτήν την αλλαγή" +includePassword = "Να συμπεριληφθεί ο νέος κωδικός πρόσβασης στο email" +forcePasswordChange = "Υποχρεωτική αλλαγή κωδικού πρόσβασης κατά την επόμενη σύνδεση" +emailUnavailable = "Το email αυτού του χρήστη δεν είναι έγκυρη διεύθυνση email. Οι ειδοποιήσεις είναι απενεργοποιημένες." +smtpDisabled = "Οι ειδοποιήσεις μέσω email απαιτούν την ενεργοποίηση του SMTP στις ρυθμίσεις." +notifyOnly = "Θα σταλεί email χωρίς τον κωδικό πρόσβασης, ενημερώνοντας τον χρήστη ότι ένας διαχειριστής τον άλλαξε." +submit = "Ενημέρωση κωδικού πρόσβασης" +success = "Ο κωδικός πρόσβασης ενημερώθηκε με επιτυχία" +error = "Αποτυχία ενημέρωσης κωδικού πρόσβασης" + [workspace.people.emailInvite] tab = "Πρόσκληση μέσω Email" description = "Πληκτρολογήστε ή επικολλήστε emails παρακάτω, χωρισμένα με κόμμα. Οι χρήστες θα λάβουν στοιχεία σύνδεσης μέσω email." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Απαιτείται τουλάχιστον μία διεύθυνση email" submit = "Αποστολή προσκλήσεων" success = "στάλθηκαν προσκλήσεις με επιτυχία" -partialSuccess = "Κάποιες προσκλήσεις απέτυχαν" +partialFailure = "Ορισμένες προσκλήσεις απέτυχαν" allFailed = "Αποτυχία πρόσκλησης χρηστών" error = "Αποτυχία αποστολής προσκλήσεων" @@ -5709,7 +5864,7 @@ title = "Διάγραμμα χρήσης Endpoints" [usage.table] title = "Αναλυτικά στατιστικά" -endpoint = "Endpoint" +endpoint = "Σημείο τερματισμού" visits = "Επισκέψεις" percentage = "Ποσοστό" noData = "Δεν υπάρχουν διαθέσιμα δεδομένα" @@ -5770,6 +5925,7 @@ subtitle = "Συνδεθείτε με τον λογαριασμό Stirling" [setup.selfhosted] title = "Σύνδεση στον διακομιστή" subtitle = "Εισαγάγετε τα διαπιστευτήρια του διακομιστή σας" +link = "ή συνδεθείτε σε έναν self-hosted λογαριασμό" [setup.server] title = "Σύνδεση σε διακομιστή" @@ -5788,6 +5944,14 @@ description = "Εισαγάγετε το πλήρες URL του self-hosted δ emptyUrl = "Εισαγάγετε URL διακομιστή" unreachable = "Αδυναμία σύνδεσης με τον διακομιστή" testFailed = "Αποτυχία ελέγχου σύνδεσης" +configFetch = "Αποτυχία ανάκτησης της διαμόρφωσης του διακομιστή. Ελέγξτε το URL και δοκιμάστε ξανά." + +[setup.server.error.securityDisabled] +title = "Η σύνδεση δεν είναι ενεργοποιημένη" +body = "Σε αυτόν τον διακομιστή δεν είναι ενεργοποιημένη η σύνδεση. Για να συνδεθείτε σε αυτόν τον διακομιστή, πρέπει να ενεργοποιήσετε τον έλεγχο ταυτότητας:" +step1 = "Ορίστε το DOCKER_ENABLE_SECURITY=true στο περιβάλλον σας" +step2 = "Ή ορίστε security.enableLogin=true στο settings.yml" +step3 = "Επανεκκινήστε τον διακομιστή" [setup.login] title = "Σύνδεση" @@ -5797,6 +5961,13 @@ submit = "Σύνδεση" signInWith = "Σύνδεση με" oauthPending = "Άνοιγμα προγράμματος περιήγησης για έλεγχο ταυτότητας..." orContinueWith = "Ή συνεχίστε με email" +serverRequirement = "Σημείωση: Ο διακομιστής πρέπει να έχει ενεργοποιημένη τη σύνδεση." +showInstructions = "Πώς ενεργοποιείται;" +hideInstructions = "Απόκρυψη οδηγιών" +instructions = "Για να ενεργοποιήσετε τη σύνδεση στον διακομιστή Stirling PDF:" +instructionsEnvVar = "Ορίστε τη μεταβλητή περιβάλλοντος:" +instructionsOrYml = "Ή στο settings.yml:" +instructionsRestart = "Στη συνέχεια, επανεκκινήστε τον διακομιστή σας για να εφαρμοστούν οι αλλαγές." [setup.login.username] label = "Όνομα χρήστη" @@ -5853,6 +6024,7 @@ earlyAccess = "Πρόωρη πρόσβαση" reset = "Επαναφορά αλλαγών" downloadJson = "Λήψη JSON" generatePdf = "Δημιουργία PDF" +saveChanges = "Αποθήκευση αλλαγών" [pdfTextEditor.options.autoScaleText] title = "Αυτόματη προσαρμογή κειμένου στα πλαίσια" @@ -5890,6 +6062,8 @@ alpha = "Αυτός ο προβολέας άλφα εξελίσσεται ακό [pdfTextEditor.empty] title = "Δεν φορτώθηκε έγγραφο" subtitle = "Φορτώστε ένα αρχείο PDF ή JSON για να ξεκινήσετε την επεξεργασία κειμένου." +dropzone = "Σύρετε και αποθέστε εδώ ένα αρχείο PDF ή JSON, ή κάντε κλικ για περιήγηση" +dropzoneWithFiles = "Επιλέξτε ένα αρχείο από την καρτέλα Αρχεία, ή σύρετε και αποθέστε εδώ ένα αρχείο PDF ή JSON, ή κάντε κλικ για περιήγηση" [pdfTextEditor.welcomeBanner] title = "Καλώς ορίσατε στο PDF Text Editor (Πρώιμη πρόσβαση)" diff --git a/frontend/public/locales/en-GB/translation.toml b/frontend/public/locales/en-GB/translation.toml index 626f589a4..f5142ef1c 100644 --- a/frontend/public/locales/en-GB/translation.toml +++ b/frontend/public/locales/en-GB/translation.toml @@ -312,10 +312,10 @@ yamlAdvert = "Stirling PDF Pro supports YAML configuration files and other SSO f ssoAdvert = "Looking for more user management features? Check out Stirling PDF Pro" [analytics] -title = "Do you want make Stirling PDF better?" -paragraph1 = "Stirling PDF has opt in analytics to help us improve the product. We do not track any personal information or file contents." +title = "Do you want to help make Stirling PDF better?" +paragraph1 = "Stirling PDF has opt-in analytics to help us improve the product. We do not track any personal information or file contents." paragraph2 = "Please consider enabling analytics to help Stirling-PDF grow and to allow us to understand our users better." -learnMore = "Learn more" +learnMore = "Learn more about our analytics" enable = "Enable analytics" disable = "Disable analytics" settings = "You can change the settings for analytics in the config/settings.yml file" @@ -340,6 +340,10 @@ advance = "Advanced" edit = "View & Edit" popular = "Popular" +[footer] +discord = "Discord" +issues = "GitHub" + [settings.preferences] title = "Preferences" @@ -435,6 +439,25 @@ latestVersion = "Latest Version" checkForUpdates = "Check for Updates" viewDetails = "View Details" +[settings.security] +title = "Security" +description = "Update your password to keep your account secure." + +[settings.security.password] +subtitle = "Change your password. You will be logged out after updating." +required = "All fields are required." +mismatch = "New passwords do not match." +error = "Unable to update password. Please verify your current password and try again." +success = "Password updated successfully. Please sign in again." +ssoDisabled = "Password changes are managed by your identity provider." +current = "Current password" +currentPlaceholder = "Enter your current password" +new = "New password" +newPlaceholder = "Enter a new password" +confirm = "Confirm new password" +confirmPlaceholder = "Re-enter your new password" +update = "Update password" + [settings.hotkeys] title = "Keyboard Shortcuts" description = "Customize keyboard shortcuts for quick tool access. Click \"Change shortcut\" and press a new key combination. Press Esc to cancel." @@ -488,11 +511,16 @@ low = "Low" title = "Change Credentials" header = "Update Your Account Details" changePassword = "You are using default login credentials. Please enter a new password" +ssoManaged = "Your account is managed by your identity provider." newUsername = "New Username" oldPassword = "Current Password" newPassword = "New Password" confirmNewPassword = "Confirm New Password" submit = "Submit Changes" +credsUpdated = "Account updated" +description = "Changes saved. Please log in again." +error = "Unable to update username. Please verify your password and try again." +changeUsername = "Update your username. You will be logged out after updating." [account] title = "Account Settings" @@ -3041,6 +3069,91 @@ title = "Get Info on PDF" header = "Get Info on PDF" submit = "Get Info" downloadJson = "Download JSON" +processing = "Extracting information..." +results = "Results" +noResults = "Run the tool to generate a report." +downloads = "Downloads" +noneDetected = "None detected" +indexTitle = "Index" + +[getPdfInfo.report] +entryLabel = "Full information summary" +shortTitle = "PDF Information" + +[getPdfInfo.sections] +metadata = "Metadata" +formFields = "Form Fields" +basicInfo = "Basic Info" +documentInfo = "Document Info" +compliance = "Compliance" +encryption = "Encryption" +permissions = "Permissions" +other = "Other" +perPageInfo = "Per Page Info" +tableOfContents = "Table of Contents" + +[getPdfInfo.other] +attachments = "Attachments" +embeddedFiles = "Embedded Files" +javaScript = "JavaScript" +layers = "Layers" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Size" +annotations = "Annotations" +images = "Images" +links = "Links" +fonts = "Fonts" +xobjects = "XObject Counts" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Pages" +fileSize = "File Size" +pdfVersion = "PDF Version" +language = "Language" +title = "PDF Summary" +author = "Author" +created = "Created" +modified = "Modified" +permsAll = "All Permissions Allowed" +permsRestricted = "{{count}} restrictions" +permsMixed = "Some permissions restricted" +hasCompliance = "Has compliance standards" +noCompliance = "No Compliance Standards" +basic = "Basic Information" +documentInfo = "Document Information" +securityTitle = "Security Status" +technical = "Technical" +overviewTitle = "PDF Overview" + +[getPdfInfo.summary.security] +encrypted = "Encrypted PDF - Password protection present" +unencrypted = "Unencrypted PDF - No password protection" + +[getPdfInfo.summary.tech] +images = "Images" +fonts = "Fonts" +formFields = "Form Fields" +embeddedFiles = "Embedded Files" +javaScript = "JavaScript" +layers = "Layers" +bookmarks = "Bookmarks" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "an untitled document" +unknown = "Unknown Author" +text = "This is a {{pages}}-page PDF titled {{title}} created by {{author}} (PDF version {{version}})." + +[getPdfInfo.error] +partial = "Some files could not be processed." +unexpected = "Unexpected error during extraction." + +[getPdfInfo.status] +complete = "Extraction complete" [extractPage] tags = "extract" @@ -3459,8 +3572,8 @@ signinTitle = "Please sign in" ssoSignIn = "Login via Single Sign-on" oAuth2AutoCreateDisabled = "OAUTH2 Auto-Create User Disabled" oAuth2AdminBlockedUser = "Registration or logging in of non-registered users is currently blocked. Please contact the administrator." -oAuth2RequiresLicense = "OAuth/SSO login requires a paid license (Server or Enterprise). Please contact the administrator to upgrade your plan." -saml2RequiresLicense = "SAML login requires a paid license (Server or Enterprise). Please contact the administrator to upgrade your plan." +oAuth2RequiresLicense = "OAuth/SSO login requires a Server or Enterprise license. Please contact the administrator to upgrade your plan." +saml2RequiresLicense = "SAML login requires an Enterprise license. Please contact the administrator to upgrade your plan." maxUsersReached = "Maximum number of users reached for your current license. Please contact the administrator to upgrade your plan or add more seats." oauth2RequestNotFound = "Authorization request not found" oauth2InvalidUserInfoResponse = "Invalid User Info Response" @@ -3623,6 +3736,16 @@ filesize = "File Size" [compress.grayscale] label = "Apply Grayscale for Compression" +[compress.lineArt] +label = "Convert images to line art" +description = "Uses ImageMagick to reduce pages to high-contrast black and white for maximum size reduction." +unavailable = "ImageMagick is not installed or enabled on this server" +detailLevel = "Detail level" +edgeEmphasis = "Edge emphasis" +edgeLow = "Gentle" +edgeMedium = "Balanced" +edgeHigh = "Strong" + [compress.tooltip.header] title = "Compress Settings Overview" @@ -3640,6 +3763,10 @@ bullet2 = "Higher values reduce file size" title = "Grayscale" text = "Select this option to convert all images to black and white, which can significantly reduce file size especially for scanned PDFs or image-heavy documents." +[compress.tooltip.lineArt] +title = "Line Art" +text = "Convert pages to high-contrast black and white using ImageMagick. Use detail level to control how much content becomes black, and edge emphasis to control how aggressively edges are detected." + [compress.error] failed = "An error occurred while compressing the PDF." @@ -4009,12 +4136,20 @@ settings = "Settings" adminSettings = "Admin Settings" allTools = "Tools" reader = "Reader" +tours = "Tours" +showMeAround = "Show me around" + +[quickAccess.toursTooltip] +admin = "Watch walkthroughs here: Tools tour, New V2 layout tour, and the Admin tour." +user = "Watch walkthroughs here: Tools tour and the New V2 layout tour." [quickAccess.helpMenu] toolsTour = "Tools Tour" toolsTourDesc = "Learn what the tools can do" adminTour = "Admin Tour" adminTourDesc = "Explore admin settings & features" +whatsNewTour = "See what's new in V2" +whatsNewTourDesc = "Tour the updated layout" [admin] error = "Error" @@ -5041,6 +5176,7 @@ loading = "Loading..." back = "Back" continue = "Continue" error = "Error" +save = "Save" [config.overview] title = "Application Configuration" @@ -5207,6 +5343,16 @@ finish = "Finish" startTour = "Start Tour" startTourDescription = "Take a guided tour of Stirling PDF's key features" +[onboarding.whatsNew] +quickAccess = "Start at the Quick Access rail to jump between Reader, Automate, your files, and all the tours." +leftPanel = "The left Tools panel lists everything you can do. Browse categories or search to find a tool quickly." +fileUpload = "Use the Files button to upload or pick a recent PDF. We will load a sample so you can see the workspace." +rightRail = "The Right Rail holds quick actions to select files, change theme or language, and download results." +topBar = "The top bar lets you swap between Viewer, Page Editor, and Active Files." +pageEditorView = "Switch to the Page Editor to reorder, rotate, or delete pages." +activeFilesView = "Use Active Files to see everything you have open and pick what to work on." +wrapUp = "That is what is new in V2. Open the Tours menu anytime to replay this, the Tools tour, or the Admin tour." + [onboarding.welcomeModal] title = "Welcome to Stirling PDF!" description = "Would you like to take a quick 1-minute tour to learn the key features and how to get started?" @@ -5227,6 +5373,10 @@ download = "Download →" showMeAround = "Show me around" skipTheTour = "Skip the tour" +[onboarding.tourOverview] +title = "Tour Overview" +body = "Stirling PDF V2 ships with dozens of tools and a refreshed layout. Take a quick tour to see what changed and where to find the features you need." + [onboarding.serverLicense] skip = "Skip for now" seePlans = "See Plans →" @@ -5234,7 +5384,7 @@ upgrade = "Upgrade now →" freeTitle = "Server License" overLimitTitle = "Server License Needed" overLimitBody = "Our licensing permits up to {{freeTierLimit}} users for free per server. You have {{overLimitUserCopy}} Stirling users. To continue uninterrupted, upgrade to the Stirling Server plan - unlimited seats, PDF text editing, and full admin control for $99/server/mo." -freeBody = "Our Open-Core licensing permits up to {{freeTierLimit}} users for free per server. To scale uninterrupted and get early access to our new PDF text editing tool, we recommend the Stirling Server plan - full editing and unlimited seats for $99/server/mo." +freeBody = "Our Open-Core licensing permits up to {{freeTierLimit}} users for free per server. To scale uninterrupted, we recommend the Stirling Server plan - unlimited seats and SSO support for $99/server/mo." [onboarding.desktopInstall] title = "Download" @@ -5339,6 +5489,31 @@ error = "Failed to update user status" success = "User deleted successfully" error = "Failed to delete user" +[workspace.people.changePassword] +action = "Change password" +title = "Change password" +subtitle = "Update the password for" +newPassword = "New password" +confirmPassword = "Confirm password" +placeholder = "Enter a new password" +confirmPlaceholder = "Re-enter the new password" +passwordRequired = "Please enter a new password" +passwordMismatch = "Passwords do not match" +generateRandom = "Generate secure password" +generatedPreview = "Generated password:" +copyTooltip = "Copy to clipboard" +copiedToClipboard = "Password copied to clipboard" +copyFailed = "Failed to copy password" +sendEmail = "Email the user about this change" +includePassword = "Include the new password in the email" +forcePasswordChange = "Force user to change password on next login" +emailUnavailable = "This user's email is not a valid email address. Notifications are disabled." +smtpDisabled = "Email notifications require SMTP to be enabled in settings." +notifyOnly = "An email will be sent without the password, letting the user know an admin changed it." +submit = "Update password" +success = "Password updated successfully" +error = "Failed to update password" + [workspace.people.emailInvite] tab = "Email Invite" description = "Type or paste in emails below, separated by commas. Users will receive login credentials via email." @@ -5515,6 +5690,28 @@ contactSales = "Contact Sales" contactToUpgrade = "Contact us to upgrade or customize your plan" maxUsers = "Max Users" upTo = "Up to" +getLicense = "Get Server License" +upgradeToEnterprise = "Upgrade to Enterprise" +selectPeriod = "Select Billing Period" +monthlyBilling = "Monthly Billing" +yearlyBilling = "Yearly Billing" +checkoutOpened = "Checkout Opened" +checkoutInstructions = "Complete your purchase in the Stripe tab. After payment, return here and refresh the page to activate your license. You will also receive an email with your license key." +activateLicense = "Activate Your License" + +[plan.static.licenseActivation] +checkoutOpened = "Checkout Opened in New Tab" +instructions = "Complete your purchase in the Stripe tab. Once your payment is complete, you will receive an email with your license key." +enterKey = "Enter your license key below to activate your plan:" +keyDescription = "Paste the license key from your email" +activate = "Activate License" +doLater = "I'll do this later" +success = "License Activated!" +successMessage = "Your license has been successfully activated. You can now close this window." + +[plan.static.billingPortal] +title = "Email Verification Required" +message = "You will need to verify your email address in the Stripe billing portal. Check your email for a login link." [plan.period] month = "month" @@ -5718,6 +5915,8 @@ notAvailable = "Audit system not available" notAvailableMessage = "The audit system is not configured or not available." disabled = "Audit logging is disabled" disabledMessage = "Enable audit logging in your application configuration to track system events." +enterpriseRequired = "Enterprise License Required" +enterpriseRequiredMessage = "The audit logging system is an enterprise feature. Please upgrade to an enterprise license to access audit logs and analytics." [audit.error] title = "Error loading audit system" @@ -5872,6 +6071,7 @@ subtitle = "Sign in with your Stirling account" [setup.selfhosted] title = "Sign in to Server" subtitle = "Enter your server credentials" +link = "or connect to a self-hosted account" [setup.server] title = "Connect to Server" @@ -5890,6 +6090,14 @@ description = "Enter the full URL of your self-hosted Stirling PDF server" emptyUrl = "Please enter a server URL" unreachable = "Could not connect to server" testFailed = "Connection test failed" +configFetch = "Failed to fetch server configuration. Please check the URL and try again." + +[setup.server.error.securityDisabled] +title = "Login Not Enabled" +body = "This server does not have login enabled. To connect to this server, you must enable authentication:" +step1 = "Set DOCKER_ENABLE_SECURITY=true in your environment" +step2 = "Or set security.enableLogin=true in settings.yml" +step3 = "Restart the server" [setup.login] title = "Sign In" @@ -5962,6 +6170,7 @@ earlyAccess = "Early Access" reset = "Reset Changes" downloadJson = "Download JSON" generatePdf = "Generate PDF" +saveChanges = "Save Changes" [pdfTextEditor.options.autoScaleText] title = "Auto-scale text to fit boxes" @@ -5999,6 +6208,8 @@ alpha = "This alpha viewer is still evolving—certain fonts, colours, transpare [pdfTextEditor.empty] title = "No document loaded" subtitle = "Load a PDF or JSON file to begin editing text content." +dropzone = "Drag and drop a PDF or JSON file here, or click to browse" +dropzoneWithFiles = "Select a file from the Files tab, or drag and drop a PDF or JSON file here, or click to browse" [pdfTextEditor.welcomeBanner] title = "Welcome to PDF Text Editor (Early Access)" diff --git a/frontend/public/locales/es-ES/translation.toml b/frontend/public/locales/es-ES/translation.toml index 7890318ff..a3faa4df1 100644 --- a/frontend/public/locales/es-ES/translation.toml +++ b/frontend/public/locales/es-ES/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Quitar de favoritos" fullscreen = "Cambiar a modo pantalla completa" sidebar = "Cambiar a modo barra lateral" +[backendStartup] +notFoundTitle = "Backend no encontrado" +retry = "Reintentar" +unreachable = "La aplicación no puede conectarse actualmente al backend. Verifique el estado del backend y la conectividad de red, luego inténtelo de nuevo." + [zipWarning] title = "Archivo ZIP grande" message = "Este ZIP contiene {{count}} archivos. ¿Extraer de todos modos?" @@ -347,7 +352,7 @@ teams = "Equipos" title = "Configuración" systemSettings = "Ajustes del sistema" features = "Funciones" -endpoints = "Endpoints" +endpoints = "Puntos de conexión" database = "Base de datos" advanced = "Avanzado" @@ -912,6 +917,9 @@ desc = "Crear flujos de trabajo de múltiples pasos encadenando acciones de PDF. desc = "Superponer PDFs encima de otro PDF" title = "Superponer PDFs" +[home.pdfTextEditor] +title = "Editor de texto de PDF" +desc = "Edita texto e imágenes existentes dentro de archivos PDF" [home.addText] tags = "texto,anotación,etiqueta" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Firma dibujada" defaultImageLabel = "Firma subida" defaultTextLabel = "Firma escrita" saveButton = "Guardar firma" +savePersonal = "Guardar personal" +saveShared = "Guardar compartida" saveUnavailable = "Cree primero una firma para guardarla." noChanges = "La firma actual ya está guardada." +tempStorageTitle = "Almacenamiento temporal del navegador" +tempStorageDescription = "Las firmas se almacenan solo en tu navegador. Se perderán si borras los datos del navegador o cambias de navegador." +personalHeading = "Firmas personales" +sharedHeading = "Firmas compartidas" +personalDescription = "Solo tú puedes ver estas firmas." +sharedDescription = "Todos los usuarios pueden ver y usar estas firmas." [sign.saved.type] canvas = "Dibujo" @@ -3020,6 +3036,91 @@ title = "Obtener Información del PDF" header = "Obtener Información del PDF" submit = "Obtener Información" downloadJson = "Descargar JSON" +processing = "Extrayendo información..." +results = "Resultados" +noResults = "Ejecute la herramienta para generar un informe." +downloads = "Descargas" +noneDetected = "Ninguno detectado" +indexTitle = "Índice" + +[getPdfInfo.report] +entryLabel = "Resumen completo de información" +shortTitle = "Información del PDF" + +[getPdfInfo.sections] +metadata = "Metadatos" +formFields = "Campos de formulario" +basicInfo = "Información básica" +documentInfo = "Información del documento" +compliance = "Conformidad" +encryption = "Cifrado" +permissions = "Permisos" +other = "Otros" +perPageInfo = "Información por página" +tableOfContents = "Tabla de contenidos" + +[getPdfInfo.other] +attachments = "Adjuntos" +embeddedFiles = "Archivos incrustados" +javaScript = "JavaScript" +layers = "Capas" +structureTree = "Árbol de estructura" +xmp = "Metadatos XMP" + +[getPdfInfo.perPage] +size = "Tamaño" +annotations = "Anotaciones" +images = "Imágenes" +links = "Enlaces" +fonts = "Fuentes" +xobjects = "Recuento de XObject" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Páginas" +fileSize = "Tamaño del archivo" +pdfVersion = "Versión de PDF" +language = "Idioma" +title = "Resumen del PDF" +author = "Autor" +created = "Creado" +modified = "Modificado" +permsAll = "Todos los permisos permitidos" +permsRestricted = "{{count}} restricciones" +permsMixed = "Algunos permisos restringidos" +hasCompliance = "Cumple con estándares" +noCompliance = "Sin estándares de conformidad" +basic = "Información básica" +documentInfo = "Información del documento" +securityTitle = "Estado de seguridad" +technical = "Técnico" +overviewTitle = "Vista general del PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF cifrado: protección con contraseña presente" +unencrypted = "PDF sin cifrar: sin protección con contraseña" + +[getPdfInfo.summary.tech] +images = "Imágenes" +fonts = "Fuentes" +formFields = "Campos de formulario" +embeddedFiles = "Archivos incrustados" +javaScript = "JavaScript" +layers = "Capas" +bookmarks = "Marcadores" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "un documento sin título" +unknown = "Autor desconocido" +text = "Este es un PDF de {{pages}} páginas titulado {{title}} creado por {{author}} (versión de PDF {{version}})." + +[getPdfInfo.error] +partial = "Algunos archivos no se pudieron procesar." +unexpected = "Error inesperado durante la extracción." + +[getPdfInfo.status] +complete = "Extracción completada" [extractPage] tags = "extraer" @@ -3438,6 +3539,9 @@ signinTitle = "Por favor, inicie sesión" ssoSignIn = "Iniciar sesión a través del inicio de sesión único" oAuth2AutoCreateDisabled = "Usuario de creación automática de OAUTH2 DESACTIVADO" oAuth2AdminBlockedUser = "El registro o inicio de sesión de usuarios no registrados está actualmente bloqueado. Por favor, póngase en contacto con el administrador." +oAuth2RequiresLicense = "El inicio de sesión OAuth/SSO requiere una licencia de pago (Server o Enterprise). Póngase en contacto con el administrador para actualizar su plan." +saml2RequiresLicense = "El inicio de sesión SAML requiere una licencia de pago (Server o Enterprise). Póngase en contacto con el administrador para actualizar su plan." +maxUsersReached = "Se alcanzó el número máximo de usuarios para su licencia actual. Póngase en contacto con el administrador para actualizar su plan o añadir más plazas." oauth2RequestNotFound = "Solicitud de autorización no encontrada" oauth2InvalidUserInfoResponse = "Respuesta de información de usuario no válida" oauth2invalidRequest = "Solicitud no válida" @@ -3846,14 +3950,17 @@ fitToWidth = "Ajustar al Ancho" actualSize = "Tamaño Real" [viewer] +cannotPreviewFile = "No se puede previsualizar el archivo" +dualPageView = "Vista de Página Doble" firstPage = "Primera Página" lastPage = "Última Página" -previousPage = "Página Anterior" nextPage = "Página Siguiente" +onlyPdfSupported = "El visor solo admite archivos PDF. Este archivo parece ser de un formato diferente." +previousPage = "Página Anterior" +singlePageView = "Vista de Página Única" +unknownFile = "Archivo desconocido" zoomIn = "Acercar" zoomOut = "Alejar" -singlePageView = "Vista de Página Única" -dualPageView = "Vista de Página Doble" [rightRail] closeSelected = "Cerrar Archivos Seleccionados" @@ -3877,6 +3984,7 @@ toggleSidebar = "Alternar Barra Lateral" exportSelected = "Exportar páginas seleccionadas" toggleAnnotations = "Mostrar/ocultar anotaciones" annotationMode = "Cambiar modo de anotaciones" +print = "Imprimir PDF" draw = "Dibujar" save = "Guardar" saveChanges = "Guardar cambios" @@ -4494,6 +4602,7 @@ description = "URL o nombre de archivo del impressum (requerido en algunas juris title = "Premium y Enterprise" description = "Configura tu clave de licencia premium o enterprise." license = "Configuración de licencia" +noInput = "Proporciona una clave o archivo de licencia" [admin.settings.premium.licenseKey] toggle = "¿Tiene una clave de licencia o un archivo de certificado?" @@ -4511,6 +4620,25 @@ line1 = "Sobrescribir su clave de licencia actual no se puede deshacer." line2 = "Su licencia anterior se perderá de forma permanente a menos que la haya respaldado en otro lugar." line3 = "Importante: mantenga las claves de licencia privadas y seguras. Nunca las comparta públicamente." +[admin.settings.premium.inputMethod] +text = "Clave de licencia" +file = "Archivo de certificado" + +[admin.settings.premium.file] +label = "Archivo de certificado de licencia" +description = "Sube tu archivo de licencia .lic o .cert de compras sin conexión" +choose = "Elegir archivo de licencia" +selected = "Seleccionado: {{filename}} ({{size}})" +successMessage = "Archivo de licencia subido y activado correctamente. No es necesario reiniciar." + +[admin.settings.premium.currentLicense] +title = "Licencia activa" +file = "Origen: Archivo de licencia ({{path}})" +key = "Origen: Clave de licencia" +type = "Tipo: {{type}}" +noInput = "Proporciona una clave de licencia o sube un archivo de certificado" +success = "Éxito" + [admin.settings.premium.enabled] label = "Habilitar funciones Premium" description = "Habilitar la verificación de la clave de licencia para funciones pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} seleccionados" download = "Descargar" delete = "Borrar" unsupported = "No Soportado" +active = "Activo" addToUpload = "Añadir a la subida" +closeFile = "Cerrar archivo" deleteAll = "Eliminar todo" loadingFiles = "Cargando archivos..." noFiles = "No hay archivos disponibles" @@ -5132,7 +5262,7 @@ upgrade = "Actualizar ahora →" freeTitle = "Licencia del servidor" overLimitTitle = "Se necesita licencia de servidor" overLimitBody = "Nuestra licencia permite hasta {{freeTierLimit}} usuarios gratis por servidor. Tiene {{overLimitUserCopy}} usuarios de Stirling. Para continuar sin interrupciones, actualice al plan Stirling Server: plazas ilimitadas, edición de texto PDF y control total de administración por 99 $/servidor/mes." -freeBody = "Nuestra licencia Open-Core permite hasta {{freeTierLimit}} usuarios gratis por servidor. Para escalar sin interrupciones y obtener acceso anticipado a nuestra nueva herramienta de edición de texto PDF, recomendamos el plan Stirling Server: edición completa y plazas ilimitadas por 99 $/servidor/mes." +freeBody = "Nuestra licencia Open-Core permite hasta {{freeTierLimit}} usuarios gratis por servidor. Para escalar sin interrupciones, recomendamos el plan Stirling Server - plazas ilimitadas y soporte SSO por $99/servidor/mes." [onboarding.desktopInstall] title = "Descargar" @@ -5204,7 +5334,7 @@ user = "Usuario" [workspace.people.addMember] title = "Añadir miembro" username = "Nombre de usuario (correo)" -usernamePlaceholder = "user@example.com" +usernamePlaceholder = "usuario@ejemplo.com" password = "Contraseña" passwordPlaceholder = "Introduce la contraseña" role = "Rol" @@ -5237,15 +5367,40 @@ error = "No se pudo actualizar el estado del usuario" success = "Usuario eliminado correctamente" error = "No se pudo eliminar el usuario" +[workspace.people.changePassword] +action = "Cambiar contraseña" +title = "Cambiar contraseña" +subtitle = "Actualizar la contraseña de" +newPassword = "Nueva contraseña" +confirmPassword = "Confirmar contraseña" +placeholder = "Introduzca una nueva contraseña" +confirmPlaceholder = "Vuelva a introducir la nueva contraseña" +passwordRequired = "Introduzca una nueva contraseña" +passwordMismatch = "Las contraseñas no coinciden" +generateRandom = "Generar contraseña segura" +generatedPreview = "Contraseña generada:" +copyTooltip = "Copiar al portapapeles" +copiedToClipboard = "Contraseña copiada al portapapeles" +copyFailed = "Error al copiar la contraseña" +sendEmail = "Enviar un correo al usuario sobre este cambio" +includePassword = "Incluir la nueva contraseña en el correo" +forcePasswordChange = "Obligar al usuario a cambiar la contraseña en el próximo inicio de sesión" +emailUnavailable = "El correo de este usuario no es una dirección válida. Las notificaciones están desactivadas." +smtpDisabled = "Las notificaciones por correo requieren que SMTP esté habilitado en la configuración." +notifyOnly = "Se enviará un correo sin la contraseña, informando al usuario de que un administrador la cambió." +submit = "Actualizar contraseña" +success = "Contraseña actualizada correctamente" +error = "No se pudo actualizar la contraseña" + [workspace.people.emailInvite] tab = "Invitación por correo electrónico" description = "Escribe o pega correos a continuación, separados por comas. Los usuarios recibirán credenciales de inicio de sesión por correo electrónico." emails = "Direcciones de correo electrónico" -emailsPlaceholder = "user1@example.com, user2@example.com" +emailsPlaceholder = "usuario1@ejemplo.com, usuario2@ejemplo.com" emailsRequired = "Se requiere al menos una dirección de correo electrónico" submit = "Enviar invitaciones" success = "usuario(s) invitado(s) correctamente" -partialSuccess = "Algunas invitaciones fallaron" +partialFailure = "Algunas invitaciones fallaron" allFailed = "No se pudo invitar a los usuarios" error = "No se pudieron enviar las invitaciones" @@ -5541,7 +5696,7 @@ emailInvalid = "Introduzca una dirección de correo válida" title = "Introduzca su correo electrónico" description = "Lo usaremos para enviar su clave de licencia y recibos." emailLabel = "Dirección de correo electrónico" -emailPlaceholder = "your@email.com" +emailPlaceholder = "su@email.com" continue = "Continuar" modalTitle = "Comenzar - {{planName}}" @@ -5770,6 +5925,7 @@ subtitle = "Inicie sesión con su cuenta de Stirling" [setup.selfhosted] title = "Inicie sesión en el servidor" subtitle = "Introduzca las credenciales de su servidor" +link = "o conectarse a una cuenta autoalojada" [setup.server] title = "Conectar con el servidor" @@ -5788,6 +5944,14 @@ description = "Introduzca la URL completa de su servidor autoalojado de Stirling emptyUrl = "Introduzca una URL de servidor" unreachable = "No se pudo conectar con el servidor" testFailed = "Falló la prueba de conexión" +configFetch = "No se pudo obtener la configuración del servidor. Compruebe la URL e inténtelo de nuevo." + +[setup.server.error.securityDisabled] +title = "Inicio de sesión no habilitado" +body = "Este servidor no tiene habilitado el inicio de sesión. Para conectarse a este servidor, debe habilitar la autenticación:" +step1 = "Establezca DOCKER_ENABLE_SECURITY=true en su entorno" +step2 = "O establezca security.enableLogin=true en settings.yml" +step3 = "Reinicie el servidor" [setup.login] title = "Iniciar sesión" @@ -5797,13 +5961,20 @@ submit = "Iniciar sesión" signInWith = "Iniciar sesión con" oauthPending = "Abriendo el navegador para autenticación..." orContinueWith = "O continuar con email" +serverRequirement = "Nota: el servidor debe tener el inicio de sesión habilitado." +showInstructions = "¿Cómo habilitarlo?" +hideInstructions = "Ocultar instrucciones" +instructions = "Para habilitar el inicio de sesión en su servidor de Stirling PDF:" +instructionsEnvVar = "Establezca la variable de entorno:" +instructionsOrYml = "O en settings.yml:" +instructionsRestart = "Luego reinicie su servidor para que los cambios surtan efecto." [setup.login.username] label = "Nombre de usuario" placeholder = "Introduzca su nombre de usuario" [setup.login.email] -label = "Email" +label = "Correo electrónico" placeholder = "Introduzca su email" [setup.login.password] @@ -5840,7 +6011,7 @@ paragraph = "Página de párrafos" sparse = "Texto disperso" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automático" paragraph = "Párrafo" singleLine = "Línea única" @@ -5853,6 +6024,7 @@ earlyAccess = "Acceso anticipado" reset = "Restablecer cambios" downloadJson = "Descargar JSON" generatePdf = "Generar PDF" +saveChanges = "Guardar cambios" [pdfTextEditor.options.autoScaleText] title = "Escalar texto automáticamente para ajustar a las cajas" @@ -5890,6 +6062,8 @@ alpha = "Este visor alfa sigue evolucionando: ciertas fuentes, colores, efectos [pdfTextEditor.empty] title = "Ningún documento cargado" subtitle = "Carga un archivo PDF o JSON para empezar a editar el contenido de texto." +dropzone = "Arrastre y suelte un archivo PDF o JSON aquí, o haga clic para explorar" +dropzoneWithFiles = "Seleccione un archivo de la pestaña Archivos, o arrastre y suelte un archivo PDF o JSON aquí, o haga clic para explorar" [pdfTextEditor.welcomeBanner] title = "Bienvenido a PDF Text Editor (Acceso anticipado)" diff --git a/frontend/public/locales/eu-ES/translation.toml b/frontend/public/locales/eu-ES/translation.toml index 89a7dddd5..154689c17 100644 --- a/frontend/public/locales/eu-ES/translation.toml +++ b/frontend/public/locales/eu-ES/translation.toml @@ -99,7 +99,7 @@ visitGithub = "Bisitatu Github biltegia" donate = "Dohaintza egin" color = "Color" sponsor = "Babestu" -info = "Info" +info = "Informazioa" pro = "Pro" page = "Orrialdea" pages = "Orrialdeak" @@ -131,7 +131,7 @@ unsupported = "Ez da onartzen" [toolPanel] placeholder = "Aukeratu tresna bat hasteko" -alpha = "Alpha" +alpha = "Alfa" premiumFeature = "Premium ezaugarria:" comingSoon = "Laster eskuragarri:" @@ -163,6 +163,11 @@ unfavorite = "Kendu gogokoetatik" fullscreen = "Aldatu pantaila osoko modura" sidebar = "Aldatu alboko barra modura" +[backendStartup] +notFoundTitle = "Backend-a ez da aurkitu" +retry = "Saiatu berriro" +unreachable = "Aplikazioak une honetan ezin du backend-arekin konektatu. Egiaztatu backend-aren egoera eta sare-konexioa, eta saiatu berriro." + [zipWarning] title = "ZIP fitxategi handia" message = "ZIP honek {{count}} fitxategi ditu. Erauzi hala ere?" @@ -274,7 +279,7 @@ iAgreeToThe = "Onartzen ditut honako hauek guztiak" terms = "Baldintzak eta erabilera-baldintzak" accessibility = "Irisgarritasuna" cookie = "Cookie politika" -impressum = "Impressum" +impressum = "Lege oharra" showCookieBanner = "Cookie-hobespenak" [pipeline] @@ -296,7 +301,7 @@ saveSettings = "Gorde eragiketa-ezarpenak" pipelineNamePrompt = "Sartu hemen pipeline izena" selectOperation = "Aukeratu eragiketa" addOperationButton = "Gehitu eragiketa" -pipelineHeader = "Pipeline:" +pipelineHeader = "Pipelinea:" saveButton = "Distira" validateButton = "Balidatu" @@ -347,7 +352,7 @@ teams = "Taldeak" title = "Konfigurazioa" systemSettings = "Sistemaren ezarpenak" features = "Eginbideak" -endpoints = "Endpoints" +endpoints = "Amaiera-puntuak" database = "Datu-basea" advanced = "Aurreratua" @@ -364,7 +369,7 @@ usageAnalytics = "Erabilera-analitika" [settings.policiesPrivacy] title = "Politikak eta Pribatutasuna" -legal = "Legal" +legal = "Lege" privacy = "Pribatutasuna" [settings.developer] @@ -513,7 +518,7 @@ syncToAccount = "Sync Kontua <- Nabigatzailea" [adminUserSettings] title = "Erabiltzailearen Ezarpenen Kontrolak" header = "Admin Erabiltzailearen Ezarpenen Kontrolak" -admin = "Admin" +admin = "Administratzailea" user = "Erabiltzaile" addUser = "Erabiltzaile berria" deleteUser = "Ezabatu erabiltzailea" @@ -912,6 +917,9 @@ desc = "Eraiki hainbat pausotako workflowak PDF ekintzak kateatuz. Egokia zeregi desc = "Overlays PDFs on-top of another PDF" title = "Gainjarri PDFak" +[home.pdfTextEditor] +title = "PDF testu editorea" +desc = "Editatu PDFetako lehendik dauden testuak eta irudiak" [home.addText] tags = "testua,anotazioa,etiketa" @@ -1217,7 +1225,7 @@ odtExt = "OpenDocument testua (.odt)" pptExt = "PowerPoint (.pptx)" odpExt = "OpenDocument aurkezpena (.odp)" txtExt = "Testu laua (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Testu aberatsaren formatua (.rtf)" selectedFiles = "Hautatutako fitxategiak" noFileSelected = "Ez da fitxategirik hautatu. Erabili fitxategi-panela fitxategiak gehitzeko." convertFiles = "Bihurtu fitxategiak" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Marrazketa sinadura" defaultImageLabel = "Igotako sinadura" defaultTextLabel = "Idatzitako sinadura" saveButton = "Gorde sinadura" +savePersonal = "Gorde pertsonala" +saveShared = "Gorde partekatua" saveUnavailable = "Lehenik sortu sinadura bat gordetzeko." noChanges = "Uneko sinadura dagoeneko gorde da." +tempStorageTitle = "Aldi baterako nabigatzaileko biltegiratzea" +tempStorageDescription = "Sinadurak zure nabigatzailean bakarrik gordetzen dira. Nabigatzailearen datuak ezabatzen badituzu edo nabigatzailea aldatzen baduzu, galdu egingo dira." +personalHeading = "Sinadura pertsonalak" +sharedHeading = "Partekatutako sinadurak" +personalDescription = "Zuk bakarrik ikus ditzakezu sinadura hauek." +sharedDescription = "Erabiltzaile guztiek ikus eta erabil ditzakete sinadura hauek." [sign.saved.type] canvas = "Marrazkia" @@ -3020,6 +3036,91 @@ title = "Lortu informazioa PDFn" header = "Lortu informazioa PDFn" submit = "Lortu informazioa" downloadJson = "Deskargatu JSON" +processing = "Informazioa erauzten..." +results = "Emaitzak" +noResults = "Exekutatu tresna txosten bat sortzeko." +downloads = "Deskargak" +noneDetected = "Ez da ezer detektatu" +indexTitle = "Indizea" + +[getPdfInfo.report] +entryLabel = "Informazio osoaren laburpena" +shortTitle = "PDFren informazioa" + +[getPdfInfo.sections] +metadata = "Metadatuak" +formFields = "Inprimaki-eremuak" +basicInfo = "Oinarrizko informazioa" +documentInfo = "Dokumentuaren informazioa" +compliance = "Arauen betetzea" +encryption = "Zifratzea" +permissions = "Baimenak" +other = "Bestelakoak" +perPageInfo = "Orrialdeko informazioa" +tableOfContents = "Aurkibidea" + +[getPdfInfo.other] +attachments = "Eranskinak" +embeddedFiles = "Txertatutako fitxategiak" +javaScript = "JavaScript" +layers = "Geruzak" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Tamaina" +annotations = "Anotazioak" +images = "Irudiak" +links = "Estekak" +fonts = "Letra-tipoak" +xobjects = "XObject kopuruak" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Orriak" +fileSize = "Fitxategi-tamaina" +pdfVersion = "PDF bertsioa" +language = "Hizkuntza" +title = "PDFren laburpena" +author = "Egilea" +created = "Sortua" +modified = "Aldatua" +permsAll = "Baimen guztiak baimenduta" +permsRestricted = "{{count}} murrizketa" +permsMixed = "Zenbait baimen murriztuta" +hasCompliance = "Betetze-estandarrak ditu" +noCompliance = "Ez dago betetze-estandarrik" +basic = "Oinarrizko informazioa" +documentInfo = "Dokumentuaren informazioa" +securityTitle = "Segurtasun-egoera" +technical = "Teknikoa" +overviewTitle = "PDFren ikuspegi orokorra" + +[getPdfInfo.summary.security] +encrypted = "Zifratutako PDFa - Pasahitz-babesa dago" +unencrypted = "Zifratu gabeko PDFa - Ez dago pasahitz-babesik" + +[getPdfInfo.summary.tech] +images = "Irudiak" +fonts = "Letra-tipoak" +formFields = "Inprimaki-eremuak" +embeddedFiles = "Txertatutako fitxategiak" +javaScript = "JavaScript" +layers = "Geruzak" +bookmarks = "Laster-markak" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "izenbururik gabeko dokumentu bat" +unknown = "Egile ezezaguna" +text = "Hau {{pages}} orrialdeko PDF bat da; izenburua: {{title}}, egilea: {{author}} (PDF bertsioa: {{version}})." + +[getPdfInfo.error] +partial = "Fitxategi batzuk ezin izan dira prozesatu." +unexpected = "Ustekabeko errorea erauzketan zehar." + +[getPdfInfo.status] +complete = "Erau zketa amaituta" [extractPage] tags = "erauzi" @@ -3438,6 +3539,9 @@ signinTitle = "Mesedez, hasi saioa" ssoSignIn = "Hasi saioa Saioa hasteko modu bakarraren bidez" oAuth2AutoCreateDisabled = "OAUTH2 Sortu automatikoki erabiltzailea desgaituta dago" oAuth2AdminBlockedUser = "Erregistratu gabeko erabiltzaileen erregistroa edo saio-hasiera une honetan blokeatuta dago. Jarri harremanetan administratzailearekin." +oAuth2RequiresLicense = "OAuth/SSO bidezko saio-hasierak lizentzia ordaindua behar du (Server edo Enterprise). Mesedez, jarri harremanetan administratzailearekin plana eguneratzeko." +saml2RequiresLicense = "SAML bidezko saio-hasierak lizentzia ordaindua behar du (Server edo Enterprise). Mesedez, jarri harremanetan administratzailearekin plana eguneratzeko." +maxUsersReached = "Zure uneko lizentziarekin erabiltzaile kopuru maximoa gainditu da. Mesedez, jarri harremanetan administratzailearekin plana eguneratzeko edo eserleku gehiago gehitzeko." oauth2RequestNotFound = "Baimen-eskaera ez da aurkitu" oauth2InvalidUserInfoResponse = "Erabiltzaile-informazioaren erantzun baliogabea" oauth2invalidRequest = "Eskaera baliogabea" @@ -3533,7 +3637,7 @@ title = "PDF Orrialde bakarrera" header = "PDF Orrialde bakarrera" submit = "Orrialde bakarrera bihurtu" description = "Tresna honek zure PDFko orri guztiak orri handi bakarrean batuko ditu. Zabalera bera izango du jatorrizko orrienarekin, baina altuera orri guztien altueren batura izango da." -filenamePrefix = "single_page" +filenamePrefix = "orrialde_bakarra" [pdfToSinglePage.files] placeholder = "Hautatu PDF fitxategi bat ikuspegi nagusian hasteko" @@ -3846,14 +3950,17 @@ fitToWidth = "Zabalera egokitu" actualSize = "Benetako tamaina" [viewer] +cannotPreviewFile = "Ezin da fitxategia aurreikusi" +dualPageView = "Orri biko ikuspegia" firstPage = "Lehen orria" lastPage = "Azken orria" -previousPage = "Aurreko orria" nextPage = "Hurrengo orria" +onlyPdfSupported = "Ikustaileak PDF fitxategiak bakarrik onartzen ditu. Fitxategi honek beste formatu batekoa dirudi." +previousPage = "Aurreko orria" +singlePageView = "Orri bakarreko ikuspegia" +unknownFile = "Fitxategi ezezaguna" zoomIn = "Zoom handitu" zoomOut = "Zoom txikitu" -singlePageView = "Orri bakarreko ikuspegia" -dualPageView = "Orri biko ikuspegia" [rightRail] closeSelected = "Itxi hautatutako fitxategiak" @@ -3877,6 +3984,7 @@ toggleSidebar = "Alboko barra txandakatu" exportSelected = "Esportatu hautatutako orriak" toggleAnnotations = "Oharpenen ikusgarritasuna txandakatu" annotationMode = "Oharpen modua txandakatu" +print = "Inprimatu PDFa" draw = "Marraztu" save = "Gorde" saveChanges = "Aldaketak gorde" @@ -4407,7 +4515,7 @@ description = "Sistema zabalagoko aldi baterako direktorioa garbitu ala ez (kont label = "Prozesu-exekutorearen mugak" description = "Konfiguratu saio-mugak eta denbora-mugak prozesu-exekutore bakoitzerako" libreOffice = "LibreOffice" -pdfToHtml = "PDF to HTML" +pdfToHtml = "PDFtik HTMLra" qpdf = "QPDF" tesseract = "Tesseract OCR" pythonOpenCv = "Python OpenCV" @@ -4487,13 +4595,14 @@ label = "Cookieen politika" description = "Cookieen politikara doan URLa edo fitxategi-izena" [admin.settings.legal.impressum] -label = "Impressum" +label = "Lege oharra" description = "Impressum-era doan URLa edo fitxategi-izena (beharrezkoa jurisdikzio batzuetan)" [admin.settings.premium] title = "Premium eta Enterprise" description = "Konfiguratu zure premium edo enterprise lizentzia-gakoa." license = "Lizentziaren konfigurazioa" +noInput = "Eman lizentzia-gakoa edo fitxategia, mesedez" [admin.settings.premium.licenseKey] toggle = "Lizentzia-gakoa edo ziurtagiri-fitxategia duzu?" @@ -4511,6 +4620,25 @@ line1 = "Uneko lizentzia-gakoa gainidaztea ezin da desegin." line2 = "Aurreko lizentzia betiko galduko da beste nonbait babestu ezean." line3 = "Garrantzitsua: Mantendu lizentzia-gakoak pribatu eta seguru. Ez partekatu publikoki inoiz." +[admin.settings.premium.inputMethod] +text = "Lizentzia-gakoa" +file = "Ziurtagiri-fitxategia" + +[admin.settings.premium.file] +label = "Lizentzia-ziurtagiriaren fitxategia" +description = "Igo zure .lic edo .cert lizentzia-fitxategia lineaz kanpoko erosketetatik" +choose = "Aukeratu lizentzia-fitxategia" +selected = "Hautatuta: {{filename}} ({{size}})" +successMessage = "Lizentzia-fitxategia behar bezala igo eta aktibatu da. Ez da berrabiaraztea beharrezkoa." + +[admin.settings.premium.currentLicense] +title = "Lizentzia aktiboa" +file = "Iturburua: Lizentzia-fitxategia ({{path}})" +key = "Iturburua: Lizentzia-gakoa" +type = "Mota: {{type}}" +noInput = "Eman lizentzia-gakoa edo igo ziurtagiri-fitxategi bat, mesedez" +success = "Arrakasta" + [admin.settings.premium.enabled] label = "Premium eginbideak gaitu" description = "Gaitu lizentzia-gakoen egiaztapenak pro/enterprise eginbideetarako" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} hautatuta" download = "Distira" delete = "ezabatu" unsupported = "Ez da onartzen" +active = "Aktibo" addToUpload = "Gehitu igoerara" +closeFile = "Itxi fitxategia" deleteAll = "Ezabatu denak" loadingFiles = "Fitxategiak kargatzen..." noFiles = "Ez dago fitxategirik eskuragarri" @@ -5132,7 +5262,7 @@ upgrade = "Eguneratu orain →" freeTitle = "Zerbitzari-lizentzia" overLimitTitle = "Beharrezkoa da zerbitzari-lizentzia" overLimitBody = "Gure lizentziak baimentzen ditu {{freeTierLimit}} erabiltzaile doan zerbitzari bakoitzeko. {{overLimitUserCopy}} Stirling erabiltzaile dituzu. Jarraitzeko etenik gabe, eguneratu Stirling Server planera - eserleku mugagabeak, PDF testu-edizioa, eta admin kontrol osoa $99/zerbitzari/hilean." -freeBody = "Gure Open-Core lizentziak {{freeTierLimit}} erabiltzaile arte baimentzen ditu doan zerbitzari bakoitzeko. Etenik gabe eskalatzeko eta gure PDF testu-edizio tresna berrirako sarbide goiztiarra lortzeko, gomendatzen dugu Stirling Server plana - edizio osoa eta eserleku mugagabeak $99/zerbitzari/hilean." +freeBody = "Gure Open-Core lizentziak zerbitzari bakoitzeko doan gehienez {{freeTierLimit}} erabiltzaile baimentzen ditu. Etenik gabe eskalatzeko, Stirling Server plana gomendatzen dugu - eserleku mugagabeak eta SSO euskarria $99/server/mo." [onboarding.desktopInstall] title = "Deskargatu" @@ -5178,7 +5308,7 @@ active = "Aktibo" disabled = "Desgaituta" activeSession = "Saio aktiboa" member = "Kidea" -admin = "Admin" +admin = "Administratzailea" editRole = "Rola editatu" enable = "Gaitu" disable = "Desgaitu" @@ -5237,6 +5367,31 @@ error = "Ezin izan da erabiltzailearen egoera eguneratu" success = "Erabiltzailea ongi ezabatu da" error = "Ezin izan da erabiltzailea ezabatu" +[workspace.people.changePassword] +action = "Pasahitza aldatu" +title = "Pasahitza aldatu" +subtitle = "Honetarako pasahitza eguneratu" +newPassword = "Pasahitz berria" +confirmPassword = "Berretsi pasahitza" +placeholder = "Sartu pasahitz berria" +confirmPlaceholder = "Sartu berriro pasahitz berria" +passwordRequired = "Sartu pasahitz berria" +passwordMismatch = "Pasahitzak ez datoz bat" +generateRandom = "Sortu pasahitz segurua" +generatedPreview = "Sortutako pasahitza:" +copyTooltip = "Kopiatu arbelera" +copiedToClipboard = "Pasahitza arbelera kopiatu da" +copyFailed = "Pasahitza kopiatzeak huts egin du" +sendEmail = "Bidali mezu elektronikoa erabiltzaileari aldaketa honi buruz" +includePassword = "Sartu pasahitz berria mezu elektronikoan" +forcePasswordChange = "Behartu erabiltzailea hurrengo saio-hasieran pasahitza aldatzera" +emailUnavailable = "Erabiltzaile honen helbide elektronikoa ez da baliozkoa. Jakinarazpenak desgaituta daude." +smtpDisabled = "Posta elektroniko bidezko jakinarazpenek SMTP gaituta egotea eskatzen dute ezarpenetan." +notifyOnly = "Pasahitzik gabe bidaliko da mezu elektronikoa; erabiltzaileari jakinaraziko zaio administratzaile batek aldatu duela." +submit = "Eguneratu pasahitza" +success = "Pasahitza ongi eguneratu da" +error = "Pasahitza eguneratzeak huts egin du" + [workspace.people.emailInvite] tab = "E-posta bidezko gonbidapena" description = "Idatzi edo itsatsi behean helbide elektronikoak, komaz bereizita. Erabiltzaileek saio-hasierako kredentzialak e-postaz jasoko dituzte." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Gutxienez helbide elektroniko bat behar da" submit = "Bidali gonbidapenak" success = "erabiltzaile(a)(k) ongi gonbidatu dira" -partialSuccess = "Gonbidapen batzuek huts egin dute" +partialFailure = "Gonbidapen batzuk huts egin dute" allFailed = "Ezin izan da erabiltzaileak gonbidatu" error = "Ezin izan dira gonbidapenak bidali" @@ -5709,7 +5864,7 @@ title = "Endpoints erabileraren diagrama" [usage.table] title = "Estatistika xeheak" -endpoint = "Endpoint" +endpoint = "Amaiera-puntua" visits = "Bisitak" percentage = "Ehunekoa" noData = "Ez dago daturik eskuragarri" @@ -5770,6 +5925,7 @@ subtitle = "Hasi saioa zure Stirling kontuarekin" [setup.selfhosted] title = "Hasi saioa zerbitzarian" subtitle = "Sartu zure zerbitzariaren kredentzialak" +link = "edo konektatu autoostatutako kontu batera" [setup.server] title = "Konektatu zerbitzarira" @@ -5788,6 +5944,14 @@ description = "Sartu zure auto-ostatuko Stirling PDF zerbitzariaren URLa osoa" emptyUrl = "Sartu zerbitzari baten URLa" unreachable = "Ezin izan da zerbitzarira konektatu" testFailed = "Konexio proba huts egin du" +configFetch = "Ezin izan da zerbitzariaren konfigurazioa eskuratu. Egiaztatu URLa eta saiatu berriro." + +[setup.server.error.securityDisabled] +title = "Saio-hasiera ez dago gaituta" +body = "Zerbitzari honek ez du saio-hasiera gaituta. Zerbitzari honekin konektatzeko, autentifikazioa gaitu behar duzu:" +step1 = "Ezarri DOCKER_ENABLE_SECURITY=true zure ingurunean" +step2 = "Edo ezarri security.enableLogin=true settings.yml fitxategian" +step3 = "Berrabiarazi zerbitzaria" [setup.login] title = "Hasi saioa" @@ -5797,6 +5961,13 @@ submit = "Hasi saioa" signInWith = "Hasi saioa honekin" oauthPending = "Nabigatzailea irekitzen autentifikaziorako..." orContinueWith = "Edo jarraitu emailarekin" +serverRequirement = "Oharra: zerbitzariak saioa hastea gaituta eduki behar du." +showInstructions = "Nola gaitu?" +hideInstructions = "Ezkutatu argibideak" +instructions = "Saioa hastea gaitzeko zure Stirling PDF zerbitzarian:" +instructionsEnvVar = "Ezarri ingurune-aldagaia:" +instructionsOrYml = "Edo settings.yml fitxategian:" +instructionsRestart = "Ondoren, berrabiarazi zerbitzaria aldaketak indarrean sartzeko." [setup.login.username] label = "Erabiltzaile-izena" @@ -5840,7 +6011,7 @@ paragraph = "Paragrafo orria" sparse = "Testu sakabanatua" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automatikoa" paragraph = "Paragrafoa" singleLine = "Lerro bakarra" @@ -5853,6 +6024,7 @@ earlyAccess = "Sarbide goiztiarra" reset = "Aldaketak berrezarri" downloadJson = "JSON deskargatu" generatePdf = "PDF sortu" +saveChanges = "Gorde aldaketak" [pdfTextEditor.options.autoScaleText] title = "Testua automatikoki eskalatu kutxetara egokitzeko" @@ -5890,6 +6062,8 @@ alpha = "Ikusle alfa hau oraindik eboluzioan dago—zenbait letra-tipo, kolore, [pdfTextEditor.empty] title = "Ez da dokumenturik kargatu" subtitle = "Kargatu PDF edo JSON fitxategi bat testu-edukia editatzen hasteko." +dropzone = "Arrastatu eta jaregin PDF edo JSON fitxategi bat hemen, edo egin klik arakatzeko" +dropzoneWithFiles = "Hautatu fitxategi bat Fitxategiak fitxatik, edo arrastatu eta jaregin PDF edo JSON fitxategi bat hemen, edo egin klik arakatzeko" [pdfTextEditor.welcomeBanner] title = "Ongi etorri PDF Text Editor-era (Sarbide goiztiarra)" diff --git a/frontend/public/locales/fa-IR/translation.toml b/frontend/public/locales/fa-IR/translation.toml index f2cb9bd91..5e363a22a 100644 --- a/frontend/public/locales/fa-IR/translation.toml +++ b/frontend/public/locales/fa-IR/translation.toml @@ -163,6 +163,11 @@ unfavorite = "حذف از علاقه‌مندی‌ها" fullscreen = "تغییر به حالت تمام‌صفحه" sidebar = "تغییر به حالت نوار کناری" +[backendStartup] +notFoundTitle = "بک‌اند یافت نشد" +retry = "تلاش مجدد" +unreachable = "برنامه در حال حاضر نمی‌تواند به بک‌اند متصل شود. وضعیت بک‌اند و اتصال شبکه را بررسی کرده و سپس دوباره تلاش کنید." + [zipWarning] title = "فایل ZIP بزرگ" message = "این ZIP شامل {{count}} فایل است. با این حال استخراج شود؟" @@ -912,6 +917,9 @@ desc = "ساخت گردش‌کارهای چندمرحله‌ای با زنجیر desc = "PDF‌ها را بر روی PDF دیگری هم‌پوشانی می‌کند" title = "هم‌پوشانی PDF‌ها" +[home.pdfTextEditor] +title = "ویرایشگر متن PDF" +desc = "ویرایش متن و تصاویر موجود در PDFها" [home.addText] tags = "متن,حاشیه‌نویسی,برچسب" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "امضای ترسیمی" defaultImageLabel = "امضای بارگذاری‌شده" defaultTextLabel = "امضای تایپی" saveButton = "ذخیره امضا" +savePersonal = "ذخیره شخصی" +saveShared = "ذخیره اشتراکی" saveUnavailable = "برای ذخیره، ابتدا امضایی بسازید." noChanges = "امضای فعلی قبلاً ذخیره شده است." +tempStorageTitle = "ذخیره‌سازی موقت در مرورگر" +tempStorageDescription = "امضاها فقط در مرورگر شما ذخیره می‌شوند. در صورت پاک‌کردن داده‌های مرورگر یا تعویض مرورگر، از بین می‌روند." +personalHeading = "امضاهای شخصی" +sharedHeading = "امضاهای اشتراکی" +personalDescription = "تنها شما می‌توانید این امضاها را ببینید." +sharedDescription = "همه کاربران می‌توانند این امضاها را ببینند و استفاده کنند." [sign.saved.type] canvas = "ترسیمی" @@ -3020,6 +3036,91 @@ title = "اطلاعات PDF را دریافت کنید" header = "اطلاعات PDF را دریافت کنید" submit = "دریافت اطلاعات" downloadJson = "دانلود JSON" +processing = "در حال استخراج اطلاعات..." +results = "نتایج" +noResults = "برای ایجاد گزارش، ابزار را اجرا کنید." +downloads = "دانلودها" +noneDetected = "هیچ موردی شناسایی نشد" +indexTitle = "نمایه" + +[getPdfInfo.report] +entryLabel = "خلاصهٔ کامل اطلاعات" +shortTitle = "اطلاعات PDF" + +[getPdfInfo.sections] +metadata = "فراداده" +formFields = "فیلدهای فرم" +basicInfo = "اطلاعات پایه" +documentInfo = "اطلاعات سند" +compliance = "انطباق" +encryption = "رمزگذاری" +permissions = "مجوزها" +other = "سایر" +perPageInfo = "اطلاعات هر صفحه" +tableOfContents = "فهرست مطالب" + +[getPdfInfo.other] +attachments = "پیوست‌ها" +embeddedFiles = "فایل‌های جاسازی‌شده" +javaScript = "JavaScript" +layers = "لایه‌ها" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "اندازه" +annotations = "حاشیه‌نویسی‌ها" +images = "تصاویر" +links = "پیوندها" +fonts = "فونت‌ها" +xobjects = "تعداد XObject" +multimedia = "چندرسانه‌ای" + +[getPdfInfo.summary] +pages = "صفحات" +fileSize = "حجم فایل" +pdfVersion = "نسخهٔ PDF" +language = "زبان" +title = "خلاصهٔ PDF" +author = "نویسنده" +created = "ایجاد شده" +modified = "ویرایش شده" +permsAll = "همهٔ مجوزها مجاز هستند" +permsRestricted = "{{count}} محدودیت" +permsMixed = "برخی مجوزها محدود شده‌اند" +hasCompliance = "دارای استانداردهای انطباق" +noCompliance = "بدون استانداردهای انطباق" +basic = "اطلاعات پایه" +documentInfo = "اطلاعات سند" +securityTitle = "وضعیت امنیتی" +technical = "فنی" +overviewTitle = "نمای کلی PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF رمزگذاری‌شده - دارای محافظت با گذرواژه" +unencrypted = "PDF رمزگذاری‌نشده - بدون محافظت با گذرواژه" + +[getPdfInfo.summary.tech] +images = "تصاویر" +fonts = "فونت‌ها" +formFields = "فیلدهای فرم" +embeddedFiles = "فایل‌های جاسازی‌شده" +javaScript = "JavaScript" +layers = "لایه‌ها" +bookmarks = "نشانک‌ها" +multimedia = "چندرسانه‌ای" + +[getPdfInfo.summary.overview] +untitled = "یک سند بدون عنوان" +unknown = "نویسندهٔ نامشخص" +text = "این یک PDF {{pages}} صفحه‌ای با عنوان {{title}} است که توسط {{author}} ایجاد شده است (نسخهٔ PDF {{version}})." + +[getPdfInfo.error] +partial = "برخی فایل‌ها قابل پردازش نبودند." +unexpected = "خطای غیرمنتظره هنگام استخراج." + +[getPdfInfo.status] +complete = "استخراج کامل شد" [extractPage] tags = "استخراج" @@ -3438,6 +3539,9 @@ signinTitle = "لطفاً وارد شوید" ssoSignIn = "ورود از طریق Single Sign-on" oAuth2AutoCreateDisabled = "ایجاد خودکار کاربر با OAUTH2 غیرفعال است" oAuth2AdminBlockedUser = "ثبت‌نام یا ورود کاربران ثبت‌نشده در حال حاضر مسدود است. لطفاً با مدیر تماس بگیرید." +oAuth2RequiresLicense = "ورود با OAuth/SSO به لایسنس پولی (Server یا Enterprise) نیاز دارد. لطفاً برای ارتقای طرح خود با مدیر تماس بگیرید." +saml2RequiresLicense = "ورود با SAML به لایسنس پولی (Server یا Enterprise) نیاز دارد. لطفاً برای ارتقای طرح خود با مدیر تماس بگیرید." +maxUsersReached = "حداکثر تعداد کاربران برای لایسنس کنونی شما به حد نصاب رسیده است. لطفاً برای ارتقای طرح یا افزودن کاربران بیشتر با مدیر تماس بگیرید." oauth2RequestNotFound = "درخواست احراز هویت پیدا نشد" oauth2InvalidUserInfoResponse = "پاسخ اطلاعات کاربری نامعتبر است" oauth2invalidRequest = "درخواست نامعتبر" @@ -3846,14 +3950,17 @@ fitToWidth = "تناسب با عرض" actualSize = "اندازه واقعی" [viewer] +cannotPreviewFile = "امکان پیش‌نمایش فایل نیست" +dualPageView = "نمای دوصفحه‌ای" firstPage = "صفحه نخست" lastPage = "صفحه آخر" -previousPage = "صفحه قبل" nextPage = "صفحه بعد" +onlyPdfSupported = "نمایشگر فقط فایل‌های PDF را پشتیبانی می‌کند. به نظر می‌رسد این فایل قالب متفاوتی دارد." +previousPage = "صفحه قبل" +singlePageView = "نمای تک‌صفحه‌ای" +unknownFile = "فایل ناشناخته" zoomIn = "بزرگ‌نمایی" zoomOut = "کوچک‌نمایی" -singlePageView = "نمای تک‌صفحه‌ای" -dualPageView = "نمای دوصفحه‌ای" [rightRail] closeSelected = "بستن فایل‌های انتخاب‌شده" @@ -3877,6 +3984,7 @@ toggleSidebar = "تغییر وضعیت نوار کناری" exportSelected = "برون‌بری صفحات انتخاب‌شده" toggleAnnotations = "تغییر وضعیت نمایش حاشیه‌نویسی‌ها" annotationMode = "تغییر حالت حاشیه‌نویسی" +print = "چاپ PDF" draw = "رسم" save = "ذخیره" saveChanges = "ذخیره تغییرات" @@ -4235,11 +4343,11 @@ label = "URL صادرکننده" description = "Issuer URL ارائه‌دهنده OAuth2" [admin.settings.connections.oauth2.clientId] -label = "Client ID" +label = "شناسهٔ کلاینت" description = "Client ID مربوط به OAuth2 از ارائه‌دهنده شما" [admin.settings.connections.oauth2.clientSecret] -label = "Client Secret" +label = "راز کلاینت" description = "Client Secret مربوط به OAuth2 از ارائه‌دهنده شما" [admin.settings.connections.oauth2.useAsUsername] @@ -4270,7 +4378,7 @@ label = "ارائه‌دهنده" description = "نام ارائه‌دهنده SAML2" [admin.settings.connections.saml2.registrationId] -label = "Registration ID" +label = "شناسهٔ ثبت‌نام" description = "شناسه ثبت‌نام SAML2" [admin.settings.connections.saml2.autoCreateUser] @@ -4487,13 +4595,14 @@ label = "خط‌مشی کوکی" description = "URL یا نام فایل برای خط‌مشی کوکی" [admin.settings.legal.impressum] -label = "Impressum" +label = "اطلاعات حقوقی" description = "URL یا نام فایل برای impressum (در برخی حوزه‌های قضایی الزامی است)" [admin.settings.premium] title = "پرمیوم و سازمانی" description = "کلید لایسنس پرمیوم یا سازمانی خود را پیکربندی کنید." license = "پیکربندی لایسنس" +noInput = "لطفاً کلید یا فایل مجوز را ارائه کنید" [admin.settings.premium.licenseKey] toggle = "کلید لایسنس یا فایل گواهی دارید؟" @@ -4511,6 +4620,25 @@ line1 = "بازنویسی کلید لایسنس فعلی قابل بازگشت line2 = "مگر آنکه در جایی پشتیبان گرفته باشید، لایسنس قبلی به‌طور دائمی از دست می‌رود." line3 = "مهم: کلیدهای لایسنس را خصوصی و امن نگه دارید. هرگز آن‌ها را عمومی به‌اشتراک نگذارید." +[admin.settings.premium.inputMethod] +text = "کلید مجوز" +file = "فایل گواهی" + +[admin.settings.premium.file] +label = "فایل گواهی مجوز" +description = "فایل مجوز .lic یا .cert مربوط به خریدهای آفلاین خود را بارگذاری کنید" +choose = "انتخاب فایل مجوز" +selected = "انتخاب‌شده: {{filename}} ({{size}})" +successMessage = "فایل مجوز با موفقیت بارگذاری و فعال شد. نیازی به راه‌اندازی مجدد نیست." + +[admin.settings.premium.currentLicense] +title = "مجوز فعال" +file = "منبع: فایل مجوز ({{path}})" +key = "منبع: کلید مجوز" +type = "نوع: {{type}}" +noInput = "لطفاً کلید مجوز ارائه کنید یا فایل گواهی را بارگذاری کنید" +success = "موفق" + [admin.settings.premium.enabled] label = "فعال‌سازی قابلیت‌های پرمیوم" description = "فعال‌سازی بررسی کلید لایسنس برای قابلیت‌های حرفه‌ای/سازمانی" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} مورد انتخاب‌شده" download = "دانلود" delete = "حذف" unsupported = "پشتیبانی‌نشده" +active = "فعال" addToUpload = "افزودن به بارگذاری" +closeFile = "بستن فایل" deleteAll = "حذف همه" loadingFiles = "در حال بارگذاری فایل‌ها..." noFiles = "فایلی موجود نیست" @@ -5132,7 +5262,7 @@ upgrade = "همین حالا ارتقا بده →" freeTitle = "لایسنس سرور" overLimitTitle = "نیاز به لایسنس سرور" overLimitBody = "مجوز ما تا {{freeTierLimit}} کاربر رایگان به‌ازای هر سرور را مجاز می‌داند. شما {{overLimitUserCopy}} کاربر Stirling دارید. برای ادامه بدون وقفه، به پلن Stirling Server ارتقا دهید - صندلی نامحدود، ویرایش متن PDF و کنترل کامل ادمین با 99$ به‌ازای هر سرور در ماه." -freeBody = "مجوز Open-Core ما تا {{freeTierLimit}} کاربر رایگان به‌ازای هر سرور را مجاز می‌داند. برای مقیاس‌پذیری بدون وقفه و دسترسی زودهنگام به ابزار ویرایش متن PDF جدیدمان، پلن Stirling Server را پیشنهاد می‌کنیم - ویرایش کامل و صندلی نامحدود با 99$ به‌ازای هر سرور در ماه." +freeBody = "مجوز Open-Core ما به‌ازای هر سرور اجازهٔ استفادهٔ رایگان برای حداکثر {{freeTierLimit}} کاربر را می‌دهد. برای مقیاس‌دهی بدون وقفه، طرح Stirling Server را توصیه می‌کنیم - تعداد کاربران نامحدود و پشتیبانی از SSO با $99/سرور/ماه." [onboarding.desktopInstall] title = "دانلود" @@ -5237,6 +5367,31 @@ error = "به‌روزرسانی وضعیت کاربر ناموفق بود" success = "کاربر با موفقیت حذف شد" error = "حذف کاربر ناموفق بود" +[workspace.people.changePassword] +action = "تغییر گذرواژه" +title = "تغییر گذرواژه" +subtitle = "به‌روزرسانی گذرواژه برای" +newPassword = "گذرواژهٔ جدید" +confirmPassword = "تأیید گذرواژه" +placeholder = "گذرواژهٔ جدید وارد کنید" +confirmPlaceholder = "گذرواژهٔ جدید را دوباره وارد کنید" +passwordRequired = "لطفاً یک گذرواژهٔ جدید وارد کنید" +passwordMismatch = "گذرواژه‌ها یکسان نیستند" +generateRandom = "ایجاد گذرواژهٔ امن" +generatedPreview = "گذرواژهٔ تولیدشده:" +copyTooltip = "کپی در کلیپ‌بورد" +copiedToClipboard = "گذرواژه در کلیپ‌بورد کپی شد" +copyFailed = "کپی گذرواژه ناموفق بود" +sendEmail = "به کاربر دربارهٔ این تغییر ایمیل ارسال کنید" +includePassword = "گذرواژهٔ جدید را در ایمیل درج کنید" +forcePasswordChange = "کاربر را ملزم کنید در ورود بعدی گذرواژه را تغییر دهد" +emailUnavailable = "ایمیل این کاربر معتبر نیست. اعلان‌ها غیرفعال شده‌اند." +smtpDisabled = "برای اعلان‌های ایمیل باید SMTP در تنظیمات فعال باشد." +notifyOnly = "ایمیلی بدون گذرواژه ارسال خواهد شد تا به کاربر اطلاع دهد که یک مدیر آن را تغییر داده است." +submit = "به‌روزرسانی گذرواژه" +success = "گذرواژه با موفقیت به‌روزرسانی شد" +error = "به‌روزرسانی گذرواژه ناموفق بود" + [workspace.people.emailInvite] tab = "دعوت ایمیلی" description = "در زیر ایمیل‌ها را تایپ یا بچسبانید و با کاما جدا کنید. به کاربران اطلاعات ورود از طریق ایمیل ارسال می‌شود." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "حداقل یک آدرس ایمیل الزامی است" submit = "ارسال دعوت‌نامه‌ها" success = "کاربر(ان) با موفقیت دعوت شد(ند)" -partialSuccess = "برخی دعوت‌ها ناموفق بود" +partialFailure = "برخی دعوت‌ها ناموفق بودند" allFailed = "دعوت کاربران ناموفق بود" error = "ارسال دعوت‌نامه‌ها ناموفق بود" @@ -5770,6 +5925,7 @@ subtitle = "با حساب Stirling خود وارد شوید" [setup.selfhosted] title = "ورود به سرور" subtitle = "اطلاعات کاربری سرور خود را وارد کنید" +link = "یا به یک حساب خودمیزبان متصل شوید" [setup.server] title = "اتصال به سرور" @@ -5788,6 +5944,14 @@ description = "URL کامل سرور خودمیزبان Stirling PDF خود را emptyUrl = "لطفاً URL سرور را وارد کنید" unreachable = "اتصال به سرور ممکن نشد" testFailed = "آزمون اتصال ناموفق بود" +configFetch = "بازیابی پیکربندی سرور ناموفق بود. لطفاً URL را بررسی کنید و دوباره تلاش کنید." + +[setup.server.error.securityDisabled] +title = "ورود فعال نیست" +body = "ورود در این سرور فعال نشده است. برای اتصال به این سرور باید احراز هویت را فعال کنید:" +step1 = "DOCKER_ENABLE_SECURITY=true را در محیط خود تنظیم کنید" +step2 = "یا security.enableLogin=true را در settings.yml تنظیم کنید" +step3 = "سرور را راه‌اندازی مجدد کنید" [setup.login] title = "ورود" @@ -5797,6 +5961,13 @@ submit = "ورود" signInWith = "ورود با" oauthPending = "در حال باز کردن مرورگر برای احراز هویت..." orContinueWith = "یا با ایمیل ادامه دهید" +serverRequirement = "توجه: سرور باید ورود را فعال کرده باشد." +showInstructions = "نحوه فعال‌سازی؟" +hideInstructions = "مخفی کردن دستورالعمل‌ها" +instructions = "برای فعال‌سازی ورود در سرور Stirling PDF خود:" +instructionsEnvVar = "متغیر محیطی را تنظیم کنید:" +instructionsOrYml = "یا در settings.yml:" +instructionsRestart = "سپس سرور خود را راه‌اندازی مجدد کنید تا تغییرات اعمال شوند." [setup.login.username] label = "نام کاربری" @@ -5853,6 +6024,7 @@ earlyAccess = "دسترسی زودهنگام" reset = "بازنشانی تغییرات" downloadJson = "دانلود JSON" generatePdf = "تولید PDF" +saveChanges = "ذخیرهٔ تغییرات" [pdfTextEditor.options.autoScaleText] title = "مقیاس خودکار متن برای جا شدن در باکس‌ها" @@ -5890,6 +6062,8 @@ alpha = "این نمایشگر آلفا هنوز در حال تکامل است [pdfTextEditor.empty] title = "هیچ سندی بارگذاری نشده است" subtitle = "برای شروع ویرایش متن، یک فایل PDF یا JSON بارگذاری کنید." +dropzone = "یک فایل PDF یا JSON را اینجا بکشید و رها کنید، یا برای مرور کلیک کنید" +dropzoneWithFiles = "یک فایل را از برگهٔ فایل‌ها انتخاب کنید، یا یک فایل PDF یا JSON را اینجا بکشید و رها کنید، یا برای مرور کلیک کنید" [pdfTextEditor.welcomeBanner] title = "به ویرایشگر متن PDF (دسترسی زودهنگام) خوش آمدید" diff --git a/frontend/public/locales/fr-FR/translation.toml b/frontend/public/locales/fr-FR/translation.toml index 2bd5da5c5..37d11a274 100644 --- a/frontend/public/locales/fr-FR/translation.toml +++ b/frontend/public/locales/fr-FR/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Retirer des favoris" fullscreen = "Passer en mode plein écran" sidebar = "Passer en mode barre latérale" +[backendStartup] +notFoundTitle = "Backend introuvable" +retry = "Réessayer" +unreachable = "L’application ne peut actuellement pas se connecter au backend. Vérifiez l’état du backend et la connectivité réseau, puis réessayez." + [zipWarning] title = "Fichier ZIP volumineux" message = "Ce ZIP contient {{count}} fichiers. Extraire quand même ?" @@ -347,7 +352,7 @@ teams = "Équipes" title = "Configuration" systemSettings = "Paramètres système" features = "Fonctionnalités" -endpoints = "Endpoints" +endpoints = "Points de terminaison" database = "Base de données" advanced = "Avancé" @@ -358,7 +363,7 @@ connections = "Connexions" [settings.licensingAnalytics] title = "Licences et analyses" -plan = "Plan" +plan = "Forfait" audit = "Audit" usageAnalytics = "Analyses d'utilisation" @@ -912,6 +917,9 @@ desc = "Créez des workflows multi-étapes en enchaînant des actions PDF. Idéa desc = "Superposer un PDF sur un autre" title = "Superposer des PDF" +[home.pdfTextEditor] +title = "Éditeur de texte PDF" +desc = "Modifier le texte et les images existants dans les PDF" [home.addText] tags = "texte,annotation,étiquette" @@ -1217,7 +1225,7 @@ odtExt = "Texte OpenDocument (.odt)" pptExt = "PowerPoint (.pptx)" odpExt = "Présentation OpenDocument (.odp)" txtExt = "Texte brut (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Format de texte enrichi (.rtf)" selectedFiles = "Fichiers sélectionnés" noFileSelected = "Aucun fichier sélectionné. Utilisez le panneau de fichiers pour ajouter des fichiers." convertFiles = "Convertir les fichiers" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Signature dessinée" defaultImageLabel = "Signature téléversée" defaultTextLabel = "Signature saisie" saveButton = "Enregistrer la signature" +savePersonal = "Enregistrer en personnel" +saveShared = "Enregistrer en partagé" saveUnavailable = "Créez d’abord une signature pour l’enregistrer." noChanges = "La signature actuelle est déjà enregistrée." +tempStorageTitle = "Stockage temporaire du navigateur" +tempStorageDescription = "Les signatures sont stockées uniquement dans votre navigateur. Elles seront perdues si vous effacez les données du navigateur ou si vous changez de navigateur." +personalHeading = "Signatures personnelles" +sharedHeading = "Signatures partagées" +personalDescription = "Vous seul pouvez voir ces signatures." +sharedDescription = "Tous les utilisateurs peuvent voir et utiliser ces signatures." [sign.saved.type] canvas = "Dessin" @@ -3020,6 +3036,91 @@ title = "Récupérer les informations" header = "Récupérer les informations" submit = "Récupérer les informations" downloadJson = "Télécharger le JSON" +processing = "Extraction des informations..." +results = "Résultats" +noResults = "Exécutez l'outil pour générer un rapport." +downloads = "Téléchargements" +noneDetected = "Aucun détecté" +indexTitle = "Index" + +[getPdfInfo.report] +entryLabel = "Résumé complet des informations" +shortTitle = "Informations PDF" + +[getPdfInfo.sections] +metadata = "Métadonnées" +formFields = "Champs de formulaire" +basicInfo = "Informations de base" +documentInfo = "Informations sur le document" +compliance = "Conformité" +encryption = "Chiffrement" +permissions = "Autorisations" +other = "Autre" +perPageInfo = "Informations par page" +tableOfContents = "Table des matières" + +[getPdfInfo.other] +attachments = "Pièces jointes" +embeddedFiles = "Fichiers intégrés" +javaScript = "JavaScript" +layers = "Calques" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Taille" +annotations = "Annotations" +images = "Images" +links = "Liens" +fonts = "Polices" +xobjects = "Nombre d'objets XObject" +multimedia = "Multimédia" + +[getPdfInfo.summary] +pages = "Pages" +fileSize = "Taille du fichier" +pdfVersion = "Version PDF" +language = "Langue" +title = "Résumé PDF" +author = "Auteur" +created = "Créé" +modified = "Modifié" +permsAll = "Toutes les autorisations accordées" +permsRestricted = "{{count}} restrictions" +permsMixed = "Certaines autorisations sont restreintes" +hasCompliance = "Respecte des normes de conformité" +noCompliance = "Aucune norme de conformité" +basic = "Informations de base" +documentInfo = "Informations sur le document" +securityTitle = "État de sécurité" +technical = "Technique" +overviewTitle = "Aperçu du PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF chiffré - Protection par mot de passe présente" +unencrypted = "PDF non chiffré - Aucune protection par mot de passe" + +[getPdfInfo.summary.tech] +images = "Images" +fonts = "Polices" +formFields = "Champs de formulaire" +embeddedFiles = "Fichiers intégrés" +javaScript = "JavaScript" +layers = "Calques" +bookmarks = "Signets" +multimedia = "Multimédia" + +[getPdfInfo.summary.overview] +untitled = "un document sans titre" +unknown = "Auteur inconnu" +text = "Ceci est un PDF de {{pages}} pages intitulé {{title}} créé par {{author}} (version PDF {{version}})." + +[getPdfInfo.error] +partial = "Certains fichiers n'ont pas pu être traités." +unexpected = "Erreur inattendue lors de l'extraction." + +[getPdfInfo.status] +complete = "Extraction terminée" [extractPage] tags = "extraire,extract" @@ -3438,6 +3539,9 @@ signinTitle = "Veuillez vous connecter" ssoSignIn = "Se connecter via l'authentification unique" oAuth2AutoCreateDisabled = "OAUTH2 Création automatique d'utilisateur désactivée" oAuth2AdminBlockedUser = "La création ou l'authentification d'utilisateurs non enregistrés est actuellement bloquée. Veuillez contacter l'administrateur." +oAuth2RequiresLicense = "La connexion OAuth/SSO nécessite une licence payante (Server ou Enterprise). Veuillez contacter l’administrateur pour mettre à niveau votre plan." +saml2RequiresLicense = "La connexion SAML nécessite une licence payante (Server ou Enterprise). Veuillez contacter l’administrateur pour mettre à niveau votre plan." +maxUsersReached = "Nombre maximal d’utilisateurs atteint pour votre licence actuelle. Veuillez contacter l’administrateur pour mettre à niveau votre plan ou ajouter des places." oauth2RequestNotFound = "Demande d'autorisation introuvable" oauth2InvalidUserInfoResponse = "Réponse contenant les informations de l'utilisateur est invalide" oauth2invalidRequest = "Requête invalide" @@ -3846,14 +3950,17 @@ fitToWidth = "Ajuster à la largeur" actualSize = "Taille réelle" [viewer] +cannotPreviewFile = "Impossible d’afficher un aperçu du fichier" +dualPageView = "Vue double page" firstPage = "Première page" lastPage = "Dernière page" -previousPage = "Page précédente" nextPage = "Page suivante" +onlyPdfSupported = "Le visualiseur prend uniquement en charge les fichiers PDF. Ce fichier semble être d’un autre format." +previousPage = "Page précédente" +singlePageView = "Vue page unique" +unknownFile = "Fichier inconnu" zoomIn = "Zoom avant" zoomOut = "Zoom arrière" -singlePageView = "Vue page unique" -dualPageView = "Vue double page" [rightRail] closeSelected = "Fermer les fichiers sélectionnés" @@ -3877,6 +3984,7 @@ toggleSidebar = "Afficher/masquer la barre latérale" exportSelected = "Exporter les pages sélectionnées" toggleAnnotations = "Afficher/masquer les annotations" annotationMode = "Basculer en mode annotation" +print = "Imprimer le PDF" draw = "Dessiner" save = "Enregistrer" saveChanges = "Enregistrer les modifications" @@ -4153,7 +4261,7 @@ description = "Suivre les actions des utilisateurs et les événements système [admin.settings.security.audit.level] label = "Niveau d’audit" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=DÉSACTIVÉ, 1=BASIQUE, 2=STANDARD, 3=VERBEUX" [admin.settings.security.audit.retentionDays] label = "Rétention des journaux (jours)" @@ -4491,9 +4599,10 @@ label = "Mentions légales" description = "URL ou nom de fichier de l’impressum (obligatoire dans certaines juridictions)" [admin.settings.premium] -title = "Premium & Enterprise" +title = "Premium et Entreprise" description = "Configurer votre clé de licence Premium ou Enterprise." license = "Configuration de la licence" +noInput = "Veuillez fournir une clé de licence ou un fichier" [admin.settings.premium.licenseKey] toggle = "Vous avez une clé de licence ou un fichier de certificat ?" @@ -4511,6 +4620,25 @@ line1 = "Écraser votre clé de licence actuelle est irréversible." line2 = "Votre licence précédente sera définitivement perdue, sauf si vous l’avez sauvegardée ailleurs." line3 = "Important : gardez vos clés de licence privées et sécurisées. Ne les partagez jamais publiquement." +[admin.settings.premium.inputMethod] +text = "Clé de licence" +file = "Fichier de certificat" + +[admin.settings.premium.file] +label = "Fichier de certificat de licence" +description = "Téléversez votre fichier de licence .lic ou .cert issu d’achats hors ligne" +choose = "Choisir le fichier de licence" +selected = "Sélectionné: {{filename}} ({{size}})" +successMessage = "Fichier de licence téléversé et activé avec succès. Aucun redémarrage requis." + +[admin.settings.premium.currentLicense] +title = "Licence active" +file = "Source: Fichier de licence ({{path}})" +key = "Source: Clé de licence" +type = "Type : {{type}}" +noInput = "Veuillez fournir une clé de licence ou téléverser un fichier de certificat" +success = "Succès" + [admin.settings.premium.enabled] label = "Activer les fonctionnalités Premium" description = "Activer la vérification de la clé de licence pour les fonctionnalités Pro/Enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} sélectionné(s)" download = "Télécharger" delete = "Supprimer" unsupported = "Non pris en charge" +active = "Actif" addToUpload = "Ajouter au téléversement" +closeFile = "Fermer le fichier" deleteAll = "Tout supprimer" loadingFiles = "Chargement des fichiers..." noFiles = "Aucun fichier disponible" @@ -5132,7 +5262,7 @@ upgrade = "Mettre à niveau maintenant →" freeTitle = "Licence serveur" overLimitTitle = "Licence serveur requise" overLimitBody = "Notre licence autorise jusqu’à {{freeTierLimit}} utilisateurs gratuits par serveur. Vous avez {{overLimitUserCopy}} utilisateurs Stirling. Pour continuer sans interruption, passez au plan Stirling Server — places illimitées, édition de texte PDF et contrôle d’administration complet pour 99 $/serveur/mois." -freeBody = "Notre licence Open-Core autorise jusqu’à {{freeTierLimit}} utilisateurs gratuits par serveur. Pour évoluer sans interruption et accéder en avant-première à notre nouvel outil d’édition de texte PDF, nous recommandons le plan Stirling Server — édition complète et places illimitées pour 99 $/serveur/mois." +freeBody = "Notre régime de licence Open-Core autorise jusqu'à {{freeTierLimit}} utilisateurs gratuitement par serveur. Pour évoluer sans interruption, nous recommandons le forfait Stirling Server - places illimitées et prise en charge du SSO pour 99 $/serveur/mois." [onboarding.desktopInstall] title = "Télécharger" @@ -5178,7 +5308,7 @@ active = "Actif" disabled = "Désactivé" activeSession = "Session active" member = "Membre" -admin = "Admin" +admin = "Administrateur" editRole = "Modifier le rôle" enable = "Activer" disable = "Désactiver" @@ -5237,6 +5367,31 @@ error = "Échec de la mise à jour du statut de l’utilisateur" success = "Utilisateur supprimé avec succès" error = "Échec de la suppression de l’utilisateur" +[workspace.people.changePassword] +action = "Changer le mot de passe" +title = "Changer le mot de passe" +subtitle = "Mettre à jour le mot de passe de" +newPassword = "Nouveau mot de passe" +confirmPassword = "Confirmer le mot de passe" +placeholder = "Saisissez un nouveau mot de passe" +confirmPlaceholder = "Saisissez à nouveau le nouveau mot de passe" +passwordRequired = "Veuillez saisir un nouveau mot de passe" +passwordMismatch = "Les mots de passe ne correspondent pas" +generateRandom = "Générer un mot de passe sécurisé" +generatedPreview = "Mot de passe généré :" +copyTooltip = "Copier dans le presse-papiers" +copiedToClipboard = "Mot de passe copié dans le presse-papiers" +copyFailed = "Échec de la copie du mot de passe" +sendEmail = "Envoyer un e-mail à l'utilisateur à propos de ce changement" +includePassword = "Inclure le nouveau mot de passe dans l'e-mail" +forcePasswordChange = "Forcer l'utilisateur à changer son mot de passe à la prochaine connexion" +emailUnavailable = "L'e-mail de cet utilisateur n'est pas une adresse valide. Les notifications sont désactivées." +smtpDisabled = "Les notifications par e-mail nécessitent l'activation de SMTP dans les paramètres." +notifyOnly = "Un e-mail sera envoyé sans le mot de passe, informant l'utilisateur qu'un administrateur l'a modifié." +submit = "Mettre à jour le mot de passe" +success = "Mot de passe mis à jour avec succès" +error = "Échec de la mise à jour du mot de passe" + [workspace.people.emailInvite] tab = "Invitation par e-mail" description = "Saisissez ou collez des adresses e-mail ci-dessous, séparées par des virgules. Les utilisateurs recevront leurs identifiants de connexion par e-mail." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Au moins une adresse e-mail est requise" submit = "Envoyer les invitations" success = "Utilisateur(s) invité(s) avec succès" -partialSuccess = "Certaines invitations ont échoué" +partialFailure = "Certaines invitations ont échoué" allFailed = "Échec de l’invitation des utilisateurs" error = "Échec de l’envoi des invitations" @@ -5709,7 +5864,7 @@ title = "Graphique d’utilisation des endpoints" [usage.table] title = "Statistiques détaillées" -endpoint = "Endpoint" +endpoint = "Point de terminaison" visits = "Visites" percentage = "Pourcentage" noData = "Aucune donnée disponible" @@ -5770,6 +5925,7 @@ subtitle = "Connectez-vous avec votre compte Stirling" [setup.selfhosted] title = "Se connecter au serveur" subtitle = "Saisissez les identifiants du serveur" +link = "ou connectez-vous à un compte auto-hébergé" [setup.server] title = "Connexion au serveur" @@ -5788,6 +5944,14 @@ description = "Saisissez l’URL complète de votre serveur Stirling PDF auto‑ emptyUrl = "Veuillez saisir une URL de serveur" unreachable = "Connexion au serveur impossible" testFailed = "Échec du test de connexion" +configFetch = "Échec de la récupération de la configuration du serveur. Veuillez vérifier l'URL et réessayer." + +[setup.server.error.securityDisabled] +title = "Connexion non activée" +body = "La connexion n'est pas activée sur ce serveur. Pour vous y connecter, vous devez activer l'authentification :" +step1 = "Définissez DOCKER_ENABLE_SECURITY=true dans votre environnement" +step2 = "Ou définissez security.enableLogin=true dans settings.yml" +step3 = "Redémarrez le serveur" [setup.login] title = "Se connecter" @@ -5797,13 +5961,20 @@ submit = "Se connecter" signInWith = "Se connecter avec" oauthPending = "Ouverture du navigateur pour l'authentification..." orContinueWith = "Ou continuer avec l’email" +serverRequirement = "Remarque : le serveur doit avoir la connexion activée." +showInstructions = "Comment l’activer ?" +hideInstructions = "Masquer les instructions" +instructions = "Pour activer la connexion sur votre serveur Stirling PDF :" +instructionsEnvVar = "Définissez la variable d’environnement :" +instructionsOrYml = "Ou dans settings.yml :" +instructionsRestart = "Redémarrez ensuite votre serveur pour que les modifications prennent effet." [setup.login.username] label = "Nom d’utilisateur" placeholder = "Entrez votre nom d’utilisateur" [setup.login.email] -label = "Email" +label = "E-mail" placeholder = "Saisissez votre email" [setup.login.password] @@ -5840,7 +6011,7 @@ paragraph = "Page de paragraphe" sparse = "Texte clairsemé" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automatique" paragraph = "Paragraphe" singleLine = "Ligne unique" @@ -5853,6 +6024,7 @@ earlyAccess = "Accès anticipé" reset = "Réinitialiser les modifications" downloadJson = "Télécharger le JSON" generatePdf = "Générer le PDF" +saveChanges = "Enregistrer les modifications" [pdfTextEditor.options.autoScaleText] title = "Ajuster automatiquement le texte aux cadres" @@ -5890,6 +6062,8 @@ alpha = "Ce visualiseur alpha évolue encore — certaines polices, couleurs, ef [pdfTextEditor.empty] title = "Aucun document chargé" subtitle = "Chargez un fichier PDF ou JSON pour commencer à modifier le texte." +dropzone = "Glissez-déposez un fichier PDF ou JSON ici, ou cliquez pour parcourir" +dropzoneWithFiles = "Sélectionnez un fichier depuis l'onglet Fichiers, ou glissez-déposez un fichier PDF ou JSON ici, ou cliquez pour parcourir" [pdfTextEditor.welcomeBanner] title = "Bienvenue dans l'éditeur de texte PDF (accès anticipé)" diff --git a/frontend/public/locales/ga-IE/translation.toml b/frontend/public/locales/ga-IE/translation.toml index d34fbfba5..868f4203a 100644 --- a/frontend/public/locales/ga-IE/translation.toml +++ b/frontend/public/locales/ga-IE/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Bain den Cheanáin" fullscreen = "Athraigh go mód lánscáileáin" sidebar = "Athraigh go mód barra taoibh" +[backendStartup] +notFoundTitle = "Níor aimsíodh an cúlchóras" +retry = "Atriail" +unreachable = "Ní féidir leis an bhfeidhmchlár ceangal leis an gcúlchóras faoi láthair. Deimhnigh stádas an chúlchórais agus nascacht an líonra, ansin bain triail eile as." + [zipWarning] title = "Comhad ZIP Mór" message = "Tá {{count}} comhad sa ZIP seo. An mbaineann tú amach mar sin féin?" @@ -912,6 +917,9 @@ desc = "Tóg sreafaí oibre ilchéime trí ghníomhartha PDF a nascadh le chéil desc = "Forleagain PDF ar bharr PDF eile" title = "Forleagan PDF" +[home.pdfTextEditor] +title = "Eagarthóir Téacs PDF" +desc = "Cuir téacs agus íomhánna atá ann cheana in eagar laistigh de PDFanna" [home.addText] tags = "téacs,anótáil,lipéad" @@ -1635,7 +1643,7 @@ subtitle = "Íoslódáil an comhad próiseáilte nó cealaigh an oibríocht thí [removePages] tags = "Bain leathanaigh, scrios leathanaigh" title = "Bain" -filenamePrefix = "pages_removed" +filenamePrefix = "leathanaigh_bainte" submit = "Bain" [removePages.pageNumbers] @@ -1837,7 +1845,7 @@ title = "Bain Léamh-Amháin ó Réimsí Foirme" header = "Díghlasáil Foirmeacha PDF" submit = "Remove" description = "Bainfidh an uirlis seo srianta léamh-amáin ó réimsí foirme PDF, rud a fhágann go mbeidh siad in-eagarthóireachta agus inlíonta." -filenamePrefix = "unlocked_forms" +filenamePrefix = "foirmeacha_díghlasáilte" [unlockPDFForms.files] placeholder = "Roghnaigh comhad PDF sa phríomh-amharc chun tosú" @@ -1851,7 +1859,7 @@ title = "Torthaí Díghlasála Foirmeacha" [changeMetadata] header = "Athraigh Meiteashonraí" submit = "Athrú" -filenamePrefix = "metadata" +filenamePrefix = "meiteashonraí" [changeMetadata.settings] title = "Socruithe Meiteashonraí" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Síniú líníochta" defaultImageLabel = "Síniú uaslódáilte" defaultTextLabel = "Síniú clóscríofa" saveButton = "Sábháil síniú" +savePersonal = "Sábháil Pearsanta" +saveShared = "Sábháil Comhroinnte" saveUnavailable = "Cruthaigh síniú ar dtús chun é a shábháil." noChanges = "Tá an síniú reatha sábháilte cheana." +tempStorageTitle = "Stóráil shealadach an bhrabhsálaí" +tempStorageDescription = "Stóráiltear na sínithe i do bhrabhsálaí amháin. Caillfear iad má ghlanann tú sonraí an bhrabhsálaí nó má athraíonn tú brabhsálaithe." +personalHeading = "Sínithe Pearsanta" +sharedHeading = "Sínithe Comhroinnte" +personalDescription = "Ní féidir ach leatsa na sínithe seo a fheiceáil." +sharedDescription = "Is féidir le gach úsáideoir na sínithe seo a fheiceáil agus a úsáid." [sign.saved.type] canvas = "Líníocht" @@ -2318,7 +2334,7 @@ title = "Comhcheangail" header = "PDF cothromú" flattenOnlyForms = "Flatten foirmeacha amháin" submit = "Comhcheangail" -filenamePrefix = "flattened" +filenamePrefix = "maolaithe" [flatten.files] placeholder = "Roghnaigh comhad PDF sa phríomh-amharc chun tosú" @@ -2366,7 +2382,7 @@ title = "Deisiúchán" header = "PDF a dheisiú" submit = "Deisiúchán" description = "Déanfaidh an uirlis seo iarracht comhaid PDF truaillithe nó damáiste a dheisiú. Níl aon socruithe breise ag teastáil." -filenamePrefix = "repaired" +filenamePrefix = "deisithe" [repair.files] placeholder = "Roghnaigh comhad PDF sa phríomh-amharc chun tosú" @@ -2567,7 +2583,7 @@ stopButton = "Stop comparáid" [certSign] tags = "fíordheimhnigh, PEM, P12, oifigiúil, criptigh" title = "Síniú Teastais" -filenamePrefix = "signed" +filenamePrefix = "síníthe" chooseCertificate = "Roghnaigh Comhad Teastais" chooseJksFile = "Roghnaigh Comhad JKS" chooseP12File = "Roghnaigh Comhad PKCS12" @@ -2701,7 +2717,7 @@ header = "Bain an deimhniú digiteach ó PDF" selectPDF = "Roghnaigh comhad PDF:" submit = "Bain Síniú" description = "Bainfidh an uirlis seo sínithe teastais dhigiteacha de do dhoiciméad PDF." -filenamePrefix = "unsigned" +filenamePrefix = "neamhshínithe" [removeCertSign.files] placeholder = "Roghnaigh comhad PDF sa phríomh-amharc chun tosú" @@ -3020,6 +3036,91 @@ title = "Faigh eolas ar PDF" header = "Faigh eolas ar PDF" submit = "Faigh Eolas" downloadJson = "Íosluchtaigh ceol JSON" +processing = "Ag eastóscadh faisnéise..." +results = "Torthaí" +noResults = "Rith an uirlis chun tuairisc a ghiniúint." +downloads = "Íoslódálacha" +noneDetected = "Níor braitheadh aon cheann" +indexTitle = "Innéacs" + +[getPdfInfo.report] +entryLabel = "Achoimre iomlán eolais" +shortTitle = "Eolas PDF" + +[getPdfInfo.sections] +metadata = "Meiteashonraí" +formFields = "Réimsí Foirme" +basicInfo = "Buneolas" +documentInfo = "Eolas faoin Doiciméad" +compliance = "Comhlíonadh" +encryption = "Criptiú" +permissions = "Ceadanna" +other = "Eile" +perPageInfo = "Eolas in aghaidh an leathanaigh" +tableOfContents = "Clár Ábhair" + +[getPdfInfo.other] +attachments = "Iatáin" +embeddedFiles = "Comhaid Leabaithe" +javaScript = "JavaScript" +layers = "Sraitheanna" +structureTree = "Crann Struchtúir" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Méid" +annotations = "Anótálacha" +images = "Íomhánna" +links = "Naisc" +fonts = "Clónna" +xobjects = "Líon XObjectanna" +multimedia = "Ilmheáin" + +[getPdfInfo.summary] +pages = "Leathanaigh" +fileSize = "Méid Comhaid" +pdfVersion = "Leagan PDF" +language = "Teanga" +title = "Achoimre PDF" +author = "Údar" +created = "Cruthaithe" +modified = "Athraithe" +permsAll = "Gach cead ceadaithe" +permsRestricted = "{{count}} srianta" +permsMixed = "Tá roinnt ceadanna srianta" +hasCompliance = "Tá caighdeáin chomhlíonta ann" +noCompliance = "Gan chaighdeáin chomhlíonta" +basic = "Buneolas" +documentInfo = "Eolas faoin Doiciméad" +securityTitle = "Stádas Slándála" +technical = "Teicniúil" +overviewTitle = "Forbhreathnú PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF criptithe - cosaint le focal faire" +unencrypted = "PDF neamchriptithe - gan chosaint le focal faire" + +[getPdfInfo.summary.tech] +images = "Íomhánna" +fonts = "Clónna" +formFields = "Réimsí Foirme" +embeddedFiles = "Comhaid Leabaithe" +javaScript = "JavaScript" +layers = "Sraitheanna" +bookmarks = "Leabharmharcanna" +multimedia = "Ilmheáin" + +[getPdfInfo.summary.overview] +untitled = "doiciméad gan teideal" +unknown = "Údar Anaithnid" +text = "Is PDF {{pages}} leathanach dar teideal {{title}} é, a chruthaigh {{author}} (leagan PDF {{version}})." + +[getPdfInfo.error] +partial = "Níorbh fhéidir roinnt comhad a phróiseáil." +unexpected = "Earráid gan choinne le linn eastósctha." + +[getPdfInfo.status] +complete = "Eastóscadh críochnaithe" [extractPage] tags = "sliocht" @@ -3438,6 +3539,9 @@ signinTitle = "Sínigh isteach le do thoil" ssoSignIn = "Logáil isteach trí Chlárú Aonair" oAuth2AutoCreateDisabled = "OAUTH2 Uath-Chruthaigh Úsáideoir faoi Mhíchumas" oAuth2AdminBlockedUser = "Tá bac faoi láthair ar chlárú nó logáil isteach úsáideoirí neamhchláraithe. Déan teagmháil leis an riarthóir le do thoil." +oAuth2RequiresLicense = "Teastaíonn ceadúnas íoctha (Server nó Enterprise) chun logáil isteach le OAuth/SSO. Déan teagmháil leis an riarthóir chun do phlean a uasghrádú." +saml2RequiresLicense = "Teastaíonn ceadúnas íoctha (Server nó Enterprise) chun logáil isteach le SAML. Déan teagmháil leis an riarthóir chun do phlean a uasghrádú." +maxUsersReached = "Sroicheadh an líon uasta úsáideoirí do do cheadúnas reatha. Déan teagmháil leis an riarthóir chun do phlean a uasghrádú nó suíocháin bhreise a chur leis." oauth2RequestNotFound = "Níor aimsíodh iarratas údaraithe" oauth2InvalidUserInfoResponse = "Freagra Neamhbhailí Faisnéise Úsáideora" oauth2invalidRequest = "Iarratas Neamhbhailí" @@ -3533,7 +3637,7 @@ title = "PDF go leathanach amháin" header = "PDF go leathanach amháin" submit = "Tiontaigh go Leathanach Aonair" description = "Cuirfidh an uirlis seo gach leathanach de do PDF le chéile in aon leathanach mór amháin. Fanfaidh an leithead mar an gcéanna leis na leathanaigh bhunaidh, ach beidh an airde cothrom le suim airde na leathanach go léir." -filenamePrefix = "single_page" +filenamePrefix = "leathanach_aonair" [pdfToSinglePage.files] placeholder = "Roghnaigh comhad PDF sa phríomh-amharc chun tosú" @@ -3846,14 +3950,17 @@ fitToWidth = "Oiriúnaigh don Leithead" actualSize = "Fíormhéid" [viewer] +cannotPreviewFile = "Ní féidir an comhad a réamhamharc." +dualPageView = "Amharc Dhá Leathanach" firstPage = "An Chéad Leathanach" lastPage = "An Leathanach Deireanach" -previousPage = "Leathanach Roimhe Seo" nextPage = "Leathanach Ar Aghaidh" +onlyPdfSupported = "Ní thacaíonn an t-amharcán ach le comhaid PDF. Is cosúil gur formáid eile é an comhad seo." +previousPage = "Leathanach Roimhe Seo" +singlePageView = "Amharc Leathanach Aonair" +unknownFile = "Comhad anaithnid" zoomIn = "Súmáil Isteach" zoomOut = "Súmáil Amach" -singlePageView = "Amharc Leathanach Aonair" -dualPageView = "Amharc Dhá Leathanach" [rightRail] closeSelected = "Dún na Comhaid Roghnaithe" @@ -3877,6 +3984,7 @@ toggleSidebar = "Athraigh an Barra Taoibh" exportSelected = "Easpórtáil na Leathanaigh Roghnaithe" toggleAnnotations = "Athraigh Infheictheacht Anótálacha" annotationMode = "Athraigh Mód Anótála" +print = "Priontáil PDF" draw = "Tarraing" save = "Sábháil" saveChanges = "Sábháil Athruithe" @@ -4494,6 +4602,7 @@ description = "URL nó ainm comhaid don impressum (riachtanach i roinnt dlínsí title = "Préimh & Fiontar" description = "Cumraigh do eochair cheadúnais préimhe nó fiontair." license = "Cumraíocht Ceadúnais" +noInput = "Tabhair eochair nó comhad ceadúnais, le do thoil" [admin.settings.premium.licenseKey] toggle = "An bhfuil eochair cheadúnais nó comhad teastais agat?" @@ -4511,6 +4620,25 @@ line1 = "Ní féidir forshcríobh ar do eochair cheadúnais reatha a chealú." line2 = "Caillefar do cheadúnas roimhe seo go buan mura bhfuil cúltaca de in áit eile agat." line3 = "Tábhachtach: Coinnigh eochracha ceadúnais príobháideach agus slán. Ná roinn go poiblí riamh." +[admin.settings.premium.inputMethod] +text = "Eochair Ceadúnais" +file = "Comhad Teastais" + +[admin.settings.premium.file] +label = "Comhad Teastais Ceadúnais" +description = "Uaslódáil do chomhad ceadúnais .lic nó .cert ó cheannacháin as líne" +choose = "Roghnaigh Comhad Ceadúnais" +selected = "Roghnaithe: {{filename}} ({{size}})" +successMessage = "D’éirigh le huaslódáil agus gníomhachtú an chomhaid cheadúnais. Níl atosú ag teastáil." + +[admin.settings.premium.currentLicense] +title = "Ceadúnas Gníomhach" +file = "Foinse: Comhad ceadúnais ({{path}})" +key = "Foinse: Eochair ceadúnais" +type = "Cineál: {{type}}" +noInput = "Tabhair eochair ceadúnais nó uaslódáil comhad teastais, le do thoil" +success = "Rath" + [admin.settings.premium.enabled] label = "Cumasaigh Gnéithe Préimhe" description = "Cumasaigh seiceálacha eochrach ceadúnais do ghnéithe pro/fiontair" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} roghnaithe" download = "Íosluchtaigh" delete = "Scrios" unsupported = "Gan tacaíocht" +active = "Gníomhach" addToUpload = "Cuir leis an Uaslódáil" +closeFile = "Dún an comhad" deleteAll = "Scrios Uile" loadingFiles = "Comhaid á Luchtú..." noFiles = "Níl comhaid ar fáil" @@ -5132,7 +5262,7 @@ upgrade = "Uasghrádaigh anois →" freeTitle = "Ceadúnas Freastalaí" overLimitTitle = "Ceadúnas Freastalaí de dhíth" overLimitBody = "Ceadaíonn ár gceadúnú suas le {{freeTierLimit}} úsáideoir in aisce in aghaidh freastalaí. Tá {{overLimitUserCopy}} úsáideoir Stirling agat. Chun leanúint gan bhriseadh, uasghrádaigh go plean Freastalaí Stirling - suíocháin neamhtheoranta, eagarthóireacht téacs PDF, agus lánrialú riaracháin ar $99/freastalaí/mí." -freeBody = "Ceadaíonn ár gceadúnú Open-Core suas le {{freeTierLimit}} úsáideoir in aisce in aghaidh freastalaí. Chun méadú gan bhriseadh agus rochtain luath a fháil ar ár uirlis eagarthóireachta téacs PDF nua, molaimid Plean Freastalaí Stirling - eagarthóireacht iomlán agus suíocháin neamhtheoranta ar $99/freastalaí/mí." +freeBody = "Ceadaíonn ár gceadúnú Open-Core suas le {{freeTierLimit}} úsáideoirí saor in aisce in aghaidh an fhreastalaí. Chun scálú gan bhriseadh, molaimid an plean Stirling Server - suíocháin neamhtheoranta agus tacaíocht SSO ar $99/server/mo." [onboarding.desktopInstall] title = "Íoslódáil" @@ -5237,6 +5367,31 @@ error = "Theip ar stádas úsáideora a nuashonrú" success = "Scriosadh an t-úsáideoir go rathúil" error = "Theip ar an úsáideoir a scriosadh" +[workspace.people.changePassword] +action = "Athraigh an focal faire" +title = "Athraigh an focal faire" +subtitle = "Nuashonraigh an focal faire do" +newPassword = "Focal faire nua" +confirmPassword = "Deimhnigh an focal faire" +placeholder = "Cuir focal faire nua isteach" +confirmPlaceholder = "Cuir an focal faire nua isteach arís" +passwordRequired = "Cuir focal faire nua isteach le do thoil" +passwordMismatch = "Ní hionann na focail faire" +generateRandom = "Gin focal faire slán" +generatedPreview = "Focal faire ginte:" +copyTooltip = "Cóipeáil chuig an ngearrthaisce" +copiedToClipboard = "Cóipeáladh an focal faire chuig an ngearrthaisce" +copyFailed = "Níor éirigh le cóipeáil an fhocail fhaire" +sendEmail = "Seol ríomhphost chuig an úsáideoir faoin athrú seo" +includePassword = "Cuir an focal faire nua san ríomhphost" +forcePasswordChange = "Cuir iallach ar an úsáideoir an focal faire a athrú ag an gcéad logáil isteach eile" +emailUnavailable = "Níl seoladh ríomhphoist bailí ag an úsáideoir seo. Tá fógraí díchumasaithe." +smtpDisabled = "Teastaíonn SMTP cumasaithe sna socruithe le haghaidh fógraí ríomhphoist." +notifyOnly = "Seolfar ríomhphost gan an focal faire, ag cur in iúl don úsáideoir gur d’athraigh riarthóir é." +submit = "Nuashonraigh an focal faire" +success = "Nuashonraíodh an focal faire go rathúil" +error = "Níor éirigh le focal faire a nuashonrú" + [workspace.people.emailInvite] tab = "Cuireadh Ríomhphoist" description = "Clóscríobh nó greamaigh seoltaí ríomhphoist thíos, scartha le camóga. Gheobhaidh úsáideoirí dintiúir logála isteach trí r-phost." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Tá ar a laghad seoladh ríomhphoist amháin de dhíth" submit = "Seol Cuirí" success = "Tugadh cuireadh d’úsáideoir(í) go rathúil" -partialSuccess = "Theip ar chuid de na cuirí" +partialFailure = "Níor éirigh le roinnt cuirí" allFailed = "Theip ar úsáideoirí a thabhairt isteach" error = "Theip ar churí a sheoladh" @@ -5288,8 +5443,8 @@ emailDisabled = "Teastaíonn cumraíocht SMTP agus mail.enableInvites=true sna s [workspace.people.license] users = "úsáideoirí" availableSlots = "Áiteanna Ar Fáil" -grandfathered = "Grandfathered" -grandfatheredShort = "{{count}} grandfathered" +grandfathered = "Ceadaithe roimhe seo" +grandfatheredShort = "{{count}} ceadaithe roimhe seo" fromLicense = "ón gceadúnas" slotsAvailable = "{{count}} áit(í) úsáideora ar fáil" noSlotsAvailable = "Níl aon áiteanna ar fáil" @@ -5770,6 +5925,7 @@ subtitle = "Sínigh isteach le do chuntas Stirling" [setup.selfhosted] title = "Sínigh isteach chuig an bhFreastalaí" subtitle = "Cuir isteach dintiúir do fhreastalaí" +link = "nó ceangail le cuntas féinóstáilte" [setup.server] title = "Ceangail leis an bhFreastalaí" @@ -5788,6 +5944,14 @@ description = "Cuir isteach URL iomlán do fhreastalaí Stirling PDF féin-óst emptyUrl = "Cuir isteach URL freastalaí le do thoil" unreachable = "Níorbh fhéidir ceangal leis an bhfreastalaí" testFailed = "Theip ar thástáil an cheangail" +configFetch = "Níor éirigh le cumraíocht an fhreastalaí a fháil. Seiceáil an URL agus bain triail eile as." + +[setup.server.error.securityDisabled] +title = "Níl an Logáil Isteach Cumasaithe" +body = "Níl logáil isteach cumasaithe ar an bhfreastalaí seo. Chun ceangal leis an bhfreastalaí seo, ní mór duit fíordheimhniú a chumasú:" +step1 = "Socraigh DOCKER_ENABLE_SECURITY=true i do thimpeallacht" +step2 = "Nó socraigh security.enableLogin=true i settings.yml" +step3 = "Atosaigh an freastalaí" [setup.login] title = "Sínigh Isteach" @@ -5797,6 +5961,13 @@ submit = "Logáil Isteach" signInWith = "Sínigh isteach le" oauthPending = "Brabhsálaí á oscailt le haghaidh fíordheimhnithe..." orContinueWith = "Nó lean ar aghaidh le ríomhphost" +serverRequirement = "Nóta: Ní mór an cumas logála isteach a bheith cumasaithe ar an bhfreastalaí." +showInstructions = "Conas é a chumasú?" +hideInstructions = "Folaigh na treoracha" +instructions = "Chun logáil isteach a chumasú ar do fhreastalaí Stirling PDF:" +instructionsEnvVar = "Socraigh an athróg chomhshaoil:" +instructionsOrYml = "Nó i settings.yml:" +instructionsRestart = "Ansin atosaigh do fhreastalaí chun go mbeidh na hathruithe i bhfeidhm." [setup.login.username] label = "Ainm Úsáideora" @@ -5853,6 +6024,7 @@ earlyAccess = "Rochtain Luath" reset = "Athshocraigh Athruithe" downloadJson = "Íoslódáil JSON" generatePdf = "Gin PDF" +saveChanges = "Sábháil Athruithe" [pdfTextEditor.options.autoScaleText] title = "Scálaigh téacs go huathoibríoch chun boscaí a fheistiú" @@ -5890,6 +6062,8 @@ alpha = "Tá an t-amharcóir alfa seo fós ag forbairt — d’fhéadfadh clónn [pdfTextEditor.empty] title = "Níl aon cháipéis luchtaithe" subtitle = "Luchtaigh comhad PDF nó JSON chun eagarthóireacht ar ábhar téacs a thosú." +dropzone = "Tarraing agus scaoil comhad PDF nó JSON anseo, nó cliceáil chun brabhsáil" +dropzoneWithFiles = "Roghnaigh comhad ón gcluaisín Comhaid, nó tarraing agus scaoil comhad PDF nó JSON anseo, nó cliceáil chun brabhsáil" [pdfTextEditor.welcomeBanner] title = "Fáilte go dtí Eagarthóir Téacs PDF (Rochtain Luath)" diff --git a/frontend/public/locales/hi-IN/translation.toml b/frontend/public/locales/hi-IN/translation.toml index f0fcf7833..c636e7eac 100644 --- a/frontend/public/locales/hi-IN/translation.toml +++ b/frontend/public/locales/hi-IN/translation.toml @@ -131,7 +131,7 @@ unsupported = "असमर्थित" [toolPanel] placeholder = "शुरू करने के लिए कोई टूल चुनें" -alpha = "Alpha" +alpha = "अल्फा" premiumFeature = "प्रीमियम फीचर:" comingSoon = "जल्द आ रहा है:" @@ -163,6 +163,11 @@ unfavorite = "पसंदीदा से हटाएं" fullscreen = "फुलस्क्रीन मोड पर स्विच करें" sidebar = "साइडबार मोड पर स्विच करें" +[backendStartup] +notFoundTitle = "बैकएंड नहीं मिला" +retry = "पुनः प्रयास करें" +unreachable = "एप्लिकेशन फिलहाल बैकएंड से कनेक्ट नहीं हो पा रहा है। कृपया बैकएंड की स्थिति और नेटवर्क कनेक्टिविटी जांचें, फिर पुनः प्रयास करें।" + [zipWarning] title = "बड़ी ZIP फ़ाइल" message = "इस ZIP में {{count}} फ़ाइलें हैं। फिर भी निकालें?" @@ -369,7 +374,7 @@ privacy = "गोपनीयता" [settings.developer] title = "डेवलपर" -apiKeys = "API Keys" +apiKeys = "API कुंजियाँ" [settings.tooltips] enableLoginFirst = "पहले लॉगिन मोड सक्षम करें" @@ -829,7 +834,7 @@ title = "PDF हस्ताक्षर सत्यापित करें" desc = "PDF दस्तावेजों में डिजिटल हस्ताक्षर और प्रमाणपत्रों को सत्यापित करें" [home.swagger] -tags = "API,documentation,test" +tags = "API,दस्तावेज़ीकरण,परीक्षण" title = "API दस्तावेज़ीकरण" desc = "API दस्तावेज़ देखें और एंडपॉइंट टेस्ट करें" @@ -878,7 +883,7 @@ title = "रंग बदलें/उलटें" desc = "PDF दस्तावेज़ों में रंगों को प्रतिस्थापित या उलटें" [home.devApi] -tags = "API,development,documentation" +tags = "API,विकास,दस्तावेज़ीकरण" title = "API" desc = "API दस्तावेज़ के लिए लिंक" @@ -912,9 +917,12 @@ desc = "PDF क्रियाओं को जोड़कर बहु-चर desc = "PDF को दूसरी PDF के ऊपर ओवरले करें" title = "PDF ओवरले करें" +[home.pdfTextEditor] +title = "PDF टेक्स्ट एडिटर" +desc = "PDF फ़ाइलों के भीतर मौजूदा टेक्स्ट और इमेज संपादित करें" [home.addText] -tags = "text,annotation,label" +tags = "पाठ,टिप्पणी,लेबल" title = "टेक्स्ट जोड़ें" desc = "अपने PDF में कहीं भी कस्टम टेक्स्ट जोड़ें" @@ -1173,7 +1181,7 @@ selectFilesPlaceholder = "शुरू करने के लिए मुख settings = "सेटिंग्स" conversionCompleted = "रूपांतरण पूरा हुआ" results = "परिणाम" -defaultFilename = "converted_file" +defaultFilename = "परिवर्तित_फ़ाइल" conversionResults = "रूपांतरण परिणाम" convertFrom = "से रूपांतरित करें" convertTo = "में रूपांतरित करें" @@ -1213,11 +1221,11 @@ pdfaDigitalSignatureWarning = "PDF में एक डिजिटल हस् fileFormat = "फ़ाइल फ़ॉर्मेट" wordDoc = "Word दस्तावेज़" wordDocExt = "Word दस्तावेज़ (.docx)" -odtExt = "OpenDocument Text (.odt)" +odtExt = "OpenDocument टेक्स्ट (.odt)" pptExt = "PowerPoint (.pptx)" -odpExt = "OpenDocument Presentation (.odp)" -txtExt = "Plain Text (.txt)" -rtfExt = "Rich Text Format (.rtf)" +odpExt = "OpenDocument प्रस्तुति (.odp)" +txtExt = "सादा पाठ (.txt)" +rtfExt = "रिच टेक्स्ट फ़ॉर्मेट (.rtf)" selectedFiles = "चयनित फ़ाइलें" noFileSelected = "कोई फ़ाइल चयनित नहीं। फ़ाइलें जोड़ने के लिए फ़ाइल पैनल का उपयोग करें।" convertFiles = "फ़ाइलें रूपांतरित करें" @@ -1360,7 +1368,7 @@ title = "वॉटरमार्क जोड़ें" desc = "PDF फ़ाइलों में टेक्स्ट या इमेज वॉटरमार्क जोड़ें" completed = "वॉटरमार्क जोड़ा गया" submit = "वॉटरमार्क जोड़ें" -filenamePrefix = "watermarked" +filenamePrefix = "वॉटरमार्क_युक्त" [watermark.error] failed = "PDF में वॉटरमार्क जोड़ते समय एक त्रुटि हुई।" @@ -1635,7 +1643,7 @@ subtitle = "प्रोसेस्ड फ़ाइल डाउनलोड [removePages] tags = "पृष्ठ निकालें,पृष्ठ हटाएं" title = "निकालें" -filenamePrefix = "pages_removed" +filenamePrefix = "पृष्ठ_हटाए_गए" submit = "निकालें" [removePages.pageNumbers] @@ -1832,12 +1840,12 @@ title = "उन्नत" tags = "कम्प्रेस,छोटा,छोटा" [unlockPDFForms] -tags = "remove,delete,form,field,readonly" +tags = "हटाएं,मिटाएं,फॉर्म,फ़ील्ड,रीड-ओनली" title = "फॉर्म फ़ील्ड से Read-Only हटाएं" header = "PDF फॉर्म अनलॉक करें" submit = "Remove" description = "यह टूल PDF फॉर्म फ़ील्ड से Read-Only प्रतिबंध हटाएगा, जिससे वे संपादन योग्य और भरने योग्य बनेंगे।" -filenamePrefix = "unlocked_forms" +filenamePrefix = "अनलॉक_फ़ॉर्म" [unlockPDFForms.files] placeholder = "शुरू करने के लिए मुख्य दृश्य में एक PDF फ़ाइल चुनें" @@ -1851,7 +1859,7 @@ title = "अनलॉक किए गए फॉर्म के परिणा [changeMetadata] header = "मेटाडेटा बदलें" submit = "बदलें" -filenamePrefix = "metadata" +filenamePrefix = "मेटाडेटा" [changeMetadata.settings] title = "मेटाडेटा सेटिंग्स" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "ड्रॉइंग हस्ताक्षर" defaultImageLabel = "अपलोड किया गया हस्ताक्षर" defaultTextLabel = "टाइप किया हुआ हस्ताक्षर" saveButton = "हस्ताक्षर सहेजें" +savePersonal = "व्यक्तिगत सहेजें" +saveShared = "साझा सहेजें" saveUnavailable = "सेव करने के लिए पहले एक हस्ताक्षर बनाएँ।" noChanges = "वर्तमान हस्ताक्षर पहले से सहेजा गया है।" +tempStorageTitle = "अस्थायी ब्राउज़र संग्रहण" +tempStorageDescription = "हस्ताक्षर केवल आपके ब्राउज़र में संग्रहीत होते हैं। ब्राउज़र डेटा साफ़ करने या ब्राउज़र बदलने पर वे खो जाएंगे।" +personalHeading = "व्यक्तिगत हस्ताक्षर" +sharedHeading = "साझा हस्ताक्षर" +personalDescription = "इन हस्ताक्षरों को केवल आप देख सकते हैं।" +sharedDescription = "सभी उपयोगकर्ता इन हस्ताक्षरों को देख और उपयोग कर सकते हैं।" [sign.saved.type] canvas = "ड्रॉइंग" @@ -2318,7 +2334,7 @@ title = "समतल करें" header = "PDF समतल करें" flattenOnlyForms = "केवल फ़ॉर्म समतल करें" submit = "समतल करें" -filenamePrefix = "flattened" +filenamePrefix = "समतलीकृत" [flatten.files] placeholder = "शुरू करने के लिए मुख्य दृश्य में एक PDF फ़ाइल चुनें" @@ -2366,7 +2382,7 @@ title = "मरम्मत" header = "PDF मरम्मत" submit = "मरम्मत" description = "यह टूल भ्रष्ट या क्षतिग्रस्त PDF फ़ाइलों की मरम्मत करने का प्रयास करेगा। कोई अतिरिक्त सेटिंग्स आवश्यक नहीं हैं।" -filenamePrefix = "repaired" +filenamePrefix = "मरम्मत_किया" [repair.files] placeholder = "शुरू करने के लिए मुख्य दृश्य में एक PDF फ़ाइल चुनें" @@ -2567,7 +2583,7 @@ stopButton = "तुलना रोकें" [certSign] tags = "प्रमाणीकरण,PEM,P12,आधिकारिक,एन्क्रिप्ट" title = "प्रमाणपत्र हस्ताक्षर" -filenamePrefix = "signed" +filenamePrefix = "हस्ताक्षरित" chooseCertificate = "प्रमाणपत्र फ़ाइल चुनें" chooseJksFile = "JKS फ़ाइल चुनें" chooseP12File = "PKCS12 फ़ाइल चुनें" @@ -2701,7 +2717,7 @@ header = "PDF से डिजिटल प्रमाणपत्र हटा selectPDF = "PDF फ़ाइल चुनें:" submit = "हस्ताक्षर हटाएं" description = "यह टूल आपके PDF दस्तावेज़ से डिजिटल प्रमाणपत्र हस्ताक्षर हटाएगा।" -filenamePrefix = "unsigned" +filenamePrefix = "अनहस्ताक्षरित" [removeCertSign.files] placeholder = "शुरू करने के लिए मुख्य दृश्य में एक PDF फ़ाइल चुनें" @@ -2731,7 +2747,7 @@ submit = "जमा करें" failed = "मल्टी-पृष्ठ लेआउट बनाते समय त्रुटि हुई।" [bookletImposition] -tags = "booklet,imposition,printing,binding,folding,signature" +tags = "बुकलेट,इम्पोज़िशन,प्रिंटिंग,बाइंडिंग,फोल्डिंग,सिग्नेचर" title = "बुकलेट इम्पोज़िशन" header = "बुकलेट इम्पोज़िशन" submit = "बुकलेट बनाएँ" @@ -2830,7 +2846,7 @@ scaleFactor = "एक पृष्ठ का ज़ूम स्तर (क् submit = "जमा करें" [adjustPageScale] -tags = "resize,modify,dimension,adapt" +tags = "आकार बदलें,संशोधित करें,आयाम,अनुकूलित करें" title = "पृष्ठ स्केल समायोजित करें" header = "पृष्ठ स्केल समायोजित करें" submit = "पृष्ठ स्केल समायोजित करें" @@ -3020,6 +3036,91 @@ title = "PDF की जानकारी प्राप्त करें" header = "PDF की जानकारी प्राप्त करें" submit = "जानकारी प्राप्त करें" downloadJson = "JSON डाउनलोड करें" +processing = "जानकारी निकाली जा रही है..." +results = "परिणाम" +noResults = "रिपोर्ट बनाने के लिए टूल चलाएँ।" +downloads = "डाउनलोड" +noneDetected = "कुछ भी पता नहीं चला" +indexTitle = "अनुक्रमणिका" + +[getPdfInfo.report] +entryLabel = "पूरी जानकारी का सारांश" +shortTitle = "PDF जानकारी" + +[getPdfInfo.sections] +metadata = "मेटाडेटा" +formFields = "फ़ॉर्म फ़ील्ड्स" +basicInfo = "मूल जानकारी" +documentInfo = "दस्तावेज़ जानकारी" +compliance = "अनुपालन" +encryption = "एन्क्रिप्शन" +permissions = "अनुमतियाँ" +other = "अन्य" +perPageInfo = "प्रति पेज जानकारी" +tableOfContents = "विषय सूची" + +[getPdfInfo.other] +attachments = "संलग्नक" +embeddedFiles = "एम्बेडेड फ़ाइलें" +javaScript = "JavaScript" +layers = "लेयर्स" +structureTree = "स्ट्रक्चर ट्री" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "आकार" +annotations = "टिप्पणियाँ" +images = "छवियाँ" +links = "लिंक" +fonts = "फ़ॉन्ट्स" +xobjects = "XObject की संख्या" +multimedia = "मल्टीमीडिया" + +[getPdfInfo.summary] +pages = "पृष्ठ" +fileSize = "फ़ाइल आकार" +pdfVersion = "PDF संस्करण" +language = "भाषा" +title = "PDF सारांश" +author = "लेखक" +created = "निर्मित" +modified = "संशोधित" +permsAll = "सभी अनुमतियाँ स्वीकृत" +permsRestricted = "{{count}} प्रतिबंध" +permsMixed = "कुछ अनुमतियाँ प्रतिबंधित" +hasCompliance = "अनुपालन मानक मौजूद हैं" +noCompliance = "कोई अनुपालन मानक नहीं" +basic = "मूल जानकारी" +documentInfo = "दस्तावेज़ जानकारी" +securityTitle = "सुरक्षा स्थिति" +technical = "तकनीकी" +overviewTitle = "PDF अवलोकन" + +[getPdfInfo.summary.security] +encrypted = "एन्क्रिप्टेड PDF - पासवर्ड सुरक्षा मौजूद है" +unencrypted = "बिना एन्क्रिप्शन वाला PDF - पासवर्ड सुरक्षा नहीं है" + +[getPdfInfo.summary.tech] +images = "छवियाँ" +fonts = "फ़ॉन्ट्स" +formFields = "फ़ॉर्म फ़ील्ड्स" +embeddedFiles = "एम्बेडेड फ़ाइलें" +javaScript = "JavaScript" +layers = "लेयर्स" +bookmarks = "बुकमार्क" +multimedia = "मल्टीमीडिया" + +[getPdfInfo.summary.overview] +untitled = "एक बिना शीर्षक वाला दस्तावेज़" +unknown = "अज्ञात लेखक" +text = "यह {{pages}}-पृष्ठ वाला PDF है जिसका शीर्षक {{title}} है, जिसे {{author}} ने बनाया है (PDF संस्करण {{version}})." + +[getPdfInfo.error] +partial = "कुछ फ़ाइलों का प्रसंस्करण नहीं हो सका।" +unexpected = "निकालते समय अप्रत्याशित त्रुटि हुई।" + +[getPdfInfo.status] +complete = "एक्सट्रैक्शन पूर्ण" [extractPage] tags = "निकालें" @@ -3380,7 +3481,7 @@ certHint = "कस्टम ट्रस्ट स्रोत के विर title = "सत्यापन सेटिंग्स" [replaceColor] -tags = "Replace Colour,Page operations,Back end,server side" +tags = "रंग बदलें,पृष्ठ संचालन,Back end,server side" [replaceColor.labels] settings = "सेटिंग्स" @@ -3438,6 +3539,9 @@ signinTitle = "कृपया साइन इन करें" ssoSignIn = "सिंगल साइन-ऑन के माध्यम से लॉगिन करें" oAuth2AutoCreateDisabled = "OAUTH2 स्वतः उपयोगकर्ता निर्माण अक्षम है" oAuth2AdminBlockedUser = "गैर-पंजीकृत उपयोगकर्ताओं का पंजीकरण या लॉगिन वर्तमान में अवरुद्ध है। कृपया व्यवस्थापक से संपर्क करें।" +oAuth2RequiresLicense = "OAuth/SSO लॉगिन के लिए पेड लाइसेंस (Server या Enterprise) आवश्यक है। कृपया अपना प्लान अपग्रेड करने के लिए व्यवस्थापक से संपर्क करें।" +saml2RequiresLicense = "SAML लॉगिन के लिए पेड लाइसेंस (Server या Enterprise) आवश्यक है। कृपया अपना प्लान अपग्रेड करने के लिए व्यवस्थापक से संपर्क करें।" +maxUsersReached = "आपके वर्तमान लाइसेंस के लिए उपयोगकर्ताओं की अधिकतम सीमा पूरी हो चुकी है। कृपया अपना प्लान अपग्रेड करने या अधिक सीटें जोड़ने के लिए व्यवस्थापक से संपर्क करें।" oauth2RequestNotFound = "प्राधिकरण अनुरोध नहीं मिला" oauth2InvalidUserInfoResponse = "अमान्य उपयोगकर्ता जानकारी प्रतिक्रिया" oauth2invalidRequest = "अमान्य अनुरोध" @@ -3533,7 +3637,7 @@ title = "PDF को एकल पृष्ठ में" header = "PDF को एकल पृष्ठ में" submit = "एकल पृष्ठ में बदलें" description = "यह टूल आपके PDF के सभी पृष्ठों को एक बड़े एकल पृष्ठ में मिला देगा। चौड़ाई मूल पृष्ठों जैसी ही रहेगी, पर ऊँचाई सभी पृष्ठ ऊँचाइयों का योग होगी।" -filenamePrefix = "single_page" +filenamePrefix = "एकल_पृष्ठ" [pdfToSinglePage.files] placeholder = "शुरू करने के लिए मुख्य दृश्य में एक PDF फ़ाइल चुनें" @@ -3771,7 +3875,7 @@ version = "वर्तमान रिलीज़" title = "API दस्तावेज़ीकरण" header = "API दस्तावेज़ीकरण" desc = "Stirling PDF API एंडपॉइंट्स देखें और परीक्षण करें" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,दस्तावेज़ीकरण,swagger,endpoints,विकास" [cookieBanner.popUp] title = "हम कुकीज़ का उपयोग कैसे करते हैं" @@ -3846,14 +3950,17 @@ fitToWidth = "चौड़ाई के अनुसार फिट करे actualSize = "वास्तविक आकार" [viewer] +cannotPreviewFile = "फ़ाइल का पूर्वावलोकन नहीं किया जा सकता" +dualPageView = "दोहरा पृष्ठ दृश्य" firstPage = "पहला पृष्ठ" lastPage = "अंतिम पृष्ठ" -previousPage = "पिछला पृष्ठ" nextPage = "अगला पृष्ठ" +onlyPdfSupported = "व्यूअर केवल PDF फ़ाइलों का समर्थन करता है। यह फ़ाइल किसी भिन्न फ़ॉर्मेट में प्रतीत होती है।" +previousPage = "पिछला पृष्ठ" +singlePageView = "एकल पृष्ठ दृश्य" +unknownFile = "अज्ञात फ़ाइल" zoomIn = "ज़ूम इन" zoomOut = "ज़ूम आउट" -singlePageView = "एकल पृष्ठ दृश्य" -dualPageView = "दोहरा पृष्ठ दृश्य" [rightRail] closeSelected = "चयनित फ़ाइलें बंद करें" @@ -3877,6 +3984,7 @@ toggleSidebar = "साइडबार टॉगल करें" exportSelected = "चयनित पृष्ठ निर्यात करें" toggleAnnotations = "एनोटेशन दृश्यता टॉगल करें" annotationMode = "एनोटेशन मोड टॉगल करें" +print = "PDF प्रिंट करें" draw = "ड्रॉ" save = "सहेजें" saveChanges = "परिवर्तनों को सहेजें" @@ -4231,15 +4339,15 @@ label = "प्रदाता" description = "प्रमाणीकरण के लिए उपयोग किया जाने वाला OAuth2 प्रदाता" [admin.settings.connections.oauth2.issuer] -label = "Issuer URL" +label = "जारीकर्ता URL" description = "OAuth2 प्रदाता का Issuer URL" [admin.settings.connections.oauth2.clientId] -label = "Client ID" +label = "क्लाइंट ID" description = "आपके प्रदाता से प्राप्त OAuth2 Client ID" [admin.settings.connections.oauth2.clientSecret] -label = "Client Secret" +label = "क्लाइंट सीक्रेट" description = "आपके प्रदाता से प्राप्त OAuth2 Client Secret" [admin.settings.connections.oauth2.useAsUsername] @@ -4407,7 +4515,7 @@ description = "विस्तृत सिस्टम टेम्प डा label = "प्रोसेस एक्सीक्यूटर सीमाएँ" description = "प्रत्येक प्रोसेस एक्सीक्यूटर के लिए सेशन सीमाएँ और टाइमआउट कॉन्फ़िगर करें" libreOffice = "LibreOffice" -pdfToHtml = "PDF to HTML" +pdfToHtml = "PDF से HTML" qpdf = "QPDF" tesseract = "Tesseract OCR" pythonOpenCv = "Python OpenCV" @@ -4494,6 +4602,7 @@ description = "Impressum का URL या फ़ाइल नाम (कुछ title = "प्रीमियम और एंटरप्राइज़" description = "अपनी प्रीमियम या एंटरप्राइज़ लाइसेंस कुंजी कॉन्फ़िगर करें।" license = "लाइसेंस कॉन्फ़िगरेशन" +noInput = "कृपया लाइसेंस कुंजी या फ़ाइल प्रदान करें" [admin.settings.premium.licenseKey] toggle = "क्या आपके पास लाइसेंस की या सर्टिफिकेट फ़ाइल है?" @@ -4511,6 +4620,25 @@ line1 = "वर्तमान लाइसेंस की को ओवरर line2 = "यदि आपने कहीं और बैकअप नहीं रखा है तो आपका पिछला लाइसेंस स्थायी रूप से खो जाएगा।" line3 = "महत्वपूर्ण: लाइसेंस की को निजी और सुरक्षित रखें। इन्हें कभी सार्वजनिक रूप से साझा न करें।" +[admin.settings.premium.inputMethod] +text = "लाइसेंस कुंजी" +file = "प्रमाणपत्र फ़ाइल" + +[admin.settings.premium.file] +label = "लाइसेंस प्रमाणपत्र फ़ाइल" +description = "ऑफ़लाइन खरीद से अपनी .lic या .cert लाइसेंस फ़ाइल अपलोड करें" +choose = "लाइसेंस फ़ाइल चुनें" +selected = "चयनित: {{filename}} ({{size}})" +successMessage = "लाइसेंस फ़ाइल सफलतापूर्वक अपलोड और सक्रिय की गई। पुनः आरंभ की आवश्यकता नहीं।" + +[admin.settings.premium.currentLicense] +title = "सक्रिय लाइसेंस" +file = "स्रोत: लाइसेंस फ़ाइल ({{path}})" +key = "स्रोत: लाइसेंस कुंजी" +type = "प्रकार: {{type}}" +noInput = "कृपया लाइसेंस कुंजी प्रदान करें या एक प्रमाणपत्र फ़ाइल अपलोड करें" +success = "सफलता" + [admin.settings.premium.enabled] label = "प्रीमियम फ़ीचर्स सक्रिय करें" description = "प्रो/एंटरप्राइज़ फ़ीचर्स के लिए लाइसेंस कुंजी जाँच सक्षम करें" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} चयनित" download = "डाउनलोड करें" delete = "हटाएं" unsupported = "असमर्थित" +active = "सक्रिय" addToUpload = "अपलोड में जोड़ें" +closeFile = "फ़ाइल बंद करें" deleteAll = "सब हटाएँ" loadingFiles = "फ़ाइलें लोड हो रही हैं..." noFiles = "कोई फ़ाइल उपलब्ध नहीं" @@ -5132,7 +5262,7 @@ upgrade = "अभी अपग्रेड करें →" freeTitle = "सर्वर लाइसेंस" overLimitTitle = "सर्वर लाइसेंस आवश्यक" overLimitBody = "हमारा लाइसेंसिंग प्रति सर्वर अधिकतम {{freeTierLimit}} उपयोगकर्ताओं को मुफ्त अनुमति देता है। आपके पास {{overLimitUserCopy}} Stirling उपयोगकर्ता हैं। बिना बाधा के जारी रखने के लिए, Stirling Server प्लान में अपग्रेड करें - अनलिमिटेड सीट्स, PDF टेक्स्ट एडिटिंग, और पूर्ण एडमिन नियंत्रण $99/server/mo में।" -freeBody = "हमारा Open-Core लाइसेंसिंग प्रति सर्वर अधिकतम {{freeTierLimit}} उपयोगकर्ताओं को मुफ्त अनुमति देता है। बिना बाधा स्केल करने और हमारे नए PDF टेक्स्ट एडिटिंग टूल की प्रारंभिक पहुँच पाने के लिए हम Stirling Server प्लान की सलाह देते हैं - पूर्ण एडिटिंग और अनलिमिटेड सीट्स $99/server/mo में।" +freeBody = "हमारा Open-Core लाइसेंसिंग प्रति सर्वर अधिकतम {{freeTierLimit}} उपयोगकर्ताओं को निःशुल्क अनुमति देता है। बिना रुकावट स्केल करने के लिए, हम Stirling Server प्लान की अनुशंसा करते हैं - असीमित सीटें और SSO समर्थन $99/सर्वर/माह पर।" [onboarding.desktopInstall] title = "डाउनलोड" @@ -5237,6 +5367,31 @@ error = "उपयोगकर्ता स्थिति अपडेट क success = "उपयोगकर्ता सफलतापूर्वक हटाया गया" error = "उपयोगकर्ता हटाने में विफल" +[workspace.people.changePassword] +action = "पासवर्ड बदलें" +title = "पासवर्ड बदलें" +subtitle = "के लिए पासवर्ड अपडेट करें" +newPassword = "नया पासवर्ड" +confirmPassword = "पासवर्ड की पुष्टि करें" +placeholder = "नया पासवर्ड दर्ज करें" +confirmPlaceholder = "नया पासवर्ड फिर से दर्ज करें" +passwordRequired = "कृपया नया पासवर्ड दर्ज करें" +passwordMismatch = "पासवर्ड मेल नहीं खाते" +generateRandom = "सुरक्षित पासवर्ड जनरेट करें" +generatedPreview = "जनरेट किया गया पासवर्ड:" +copyTooltip = "क्लिपबोर्ड पर कॉपी करें" +copiedToClipboard = "पासवर्ड क्लिपबोर्ड पर कॉपी किया गया" +copyFailed = "पासवर्ड कॉपी करने में विफल" +sendEmail = "इस बदलाव के बारे में उपयोगकर्ता को ईमेल करें" +includePassword = "ईमेल में नया पासवर्ड शामिल करें" +forcePasswordChange = "अगले लॉगिन पर उपयोगकर्ता को पासवर्ड बदलने के लिए बाध्य करें" +emailUnavailable = "इस उपयोगकर्ता का ईमेल एक मान्य ईमेल पता नहीं है। सूचनाएँ अक्षम हैं।" +smtpDisabled = "ईमेल सूचनाओं के लिए सेटिंग्स में SMTP सक्षम होना आवश्यक है।" +notifyOnly = "पासवर्ड के बिना एक ईमेल भेजा जाएगा, जिससे उपयोगकर्ता को पता चलेगा कि किसी व्यवस्थापक ने इसे बदला है।" +submit = "पासवर्ड अपडेट करें" +success = "पासवर्ड सफलतापूर्वक अपडेट किया गया" +error = "पासवर्ड अपडेट करने में विफल" + [workspace.people.emailInvite] tab = "ईमेल आमंत्रण" description = "नीचे ईमेल टाइप या पेस्ट करें, अल्पविराम से अलग करें। उपयोगकर्ताओं को ईमेल के माध्यम से लॉगिन क्रेडेंशियल मिलेंगे।" @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "कम से कम एक ईमेल पता आवश्यक है" submit = "आमंत्रण भेजें" success = "उपयोगकर्ता(ओं) को सफलतापूर्वक आमंत्रित किया गया" -partialSuccess = "कुछ आमंत्रण विफल रहे" +partialFailure = "कुछ निमंत्रण विफल हुए" allFailed = "उपयोगकर्ताओं को आमंत्रित करने में विफल" error = "आमंत्रण भेजने में विफल" @@ -5770,6 +5925,7 @@ subtitle = "अपने Stirling खाते से साइन इन कर [setup.selfhosted] title = "सर्वर में साइन इन" subtitle = "अपने सर्वर क्रेडेंशियल्स दर्ज करें" +link = "या किसी स्व-होस्टेड खाते से कनेक्ट करें" [setup.server] title = "सर्वर से कनेक्ट करें" @@ -5788,6 +5944,14 @@ description = "अपने सेल्फ-होस्टेड Stirling PDF emptyUrl = "कृपया सर्वर URL दर्ज करें" unreachable = "सर्वर से कनेक्ट नहीं हो सका" testFailed = "कनेक्शन परीक्षण विफल" +configFetch = "सर्वर कॉन्फ़िगरेशन प्राप्त करने में विफल। कृपया URL जाँचें और फिर से प्रयास करें।" + +[setup.server.error.securityDisabled] +title = "लॉगिन सक्षम नहीं है" +body = "इस सर्वर पर लॉगिन सक्षम नहीं है। इस सर्वर से कनेक्ट करने के लिए, आपको प्रमाणीकरण सक्षम करना होगा:" +step1 = "अपने एनवायरनमेंट में DOCKER_ENABLE_SECURITY=true सेट करें" +step2 = "या settings.yml में security.enableLogin=true सेट करें" +step3 = "सर्वर को पुनः प्रारंभ करें" [setup.login] title = "साइन इन" @@ -5797,6 +5961,13 @@ submit = "लॉगिन" signInWith = "इसके साथ साइन इन करें" oauthPending = "प्रमाणीकरण के लिए ब्राउज़र खुल रहा है..." orContinueWith = "या ईमेल के साथ जारी रखें" +serverRequirement = "ध्यान दें: सर्वर पर लॉगिन सक्षम होना चाहिए।" +showInstructions = "कैसे सक्षम करें?" +hideInstructions = "निर्देश छिपाएँ" +instructions = "अपने Stirling PDF सर्वर पर लॉगिन सक्षम करने के लिए:" +instructionsEnvVar = "एन्वायरनमेंट वेरिएबल सेट करें:" +instructionsOrYml = "या settings.yml में:" +instructionsRestart = "इसके बाद बदलाव प्रभावी करने के लिए अपना सर्वर पुनः प्रारंभ करें।" [setup.login.username] label = "उपयोगकर्ता नाम" @@ -5853,6 +6024,7 @@ earlyAccess = "अर्ली एक्सेस" reset = "परिवर्तन रीसेट करें" downloadJson = "JSON डाउनलोड करें" generatePdf = "PDF जनरेट करें" +saveChanges = "परिवर्तन सहेजें" [pdfTextEditor.options.autoScaleText] title = "टेक्स्ट को बॉक्स में फिट करने हेतु ऑटो-स्केल" @@ -5890,6 +6062,8 @@ alpha = "यह alpha व्यूअर अभी विकसित हो र [pdfTextEditor.empty] title = "कोई दस्तावेज़ लोड नहीं" subtitle = "टेक्स्ट सामग्री संपादित करने के लिए PDF या JSON फ़ाइल लोड करें." +dropzone = "यहाँ PDF या JSON फ़ाइल खींचकर छोड़ें, या ब्राउज़ करने के लिए क्लिक करें" +dropzoneWithFiles = "फ़ाइलें टैब से कोई फ़ाइल चुनें, या यहाँ PDF या JSON फ़ाइल खींचकर छोड़ें, या ब्राउज़ करने के लिए क्लिक करें" [pdfTextEditor.welcomeBanner] title = "PDF Text Editor में आपका स्वागत है (Early Access)" @@ -5932,7 +6106,7 @@ warnings = "चेतावनियाँ" suggestions = "नोट्स" currentPageFonts = "इस पेज के फ़ॉन्ट्स" allFonts = "सभी फ़ॉन्ट्स" -fallback = "fallback" +fallback = "फॉलबैक" missing = "गायब" perfectMessage = "सभी फ़ॉन्ट्स को पूरी तरह पुनरुत्पादित किया जा सकता है." warningMessage = "कुछ फ़ॉन्ट्स सही से रेंडर नहीं हो सकते हैं." @@ -5953,7 +6127,7 @@ insufficientPermissions = "आपके पास यह क्रिया क [addText] title = "टेक्स्ट जोड़ें" header = "PDFs में टेक्स्ट जोड़ें" -tags = "text,annotation,label" +tags = "पाठ,टिप्पणी,लेबल" applySignatures = "टेक्स्ट लागू करें" [addText.text] diff --git a/frontend/public/locales/hr-HR/translation.toml b/frontend/public/locales/hr-HR/translation.toml index c19e67180..a523fc1b7 100644 --- a/frontend/public/locales/hr-HR/translation.toml +++ b/frontend/public/locales/hr-HR/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Ukloni iz omiljenih" fullscreen = "Prebaci na način cijelog zaslona" sidebar = "Prebaci na način bočne trake" +[backendStartup] +notFoundTitle = "Backend nije pronađen" +retry = "Pokušaj ponovno" +unreachable = "Aplikacija se trenutačno ne može povezati s backendom. Provjerite status backenda i mrežnu povezanost, zatim pokušajte ponovno." + [zipWarning] title = "Velika ZIP datoteka" message = "Ovaj ZIP sadrži {{count}} datoteka. Ipak izdvojiti?" @@ -912,6 +917,9 @@ desc = "Izgradite višekoračne tijekove rada povezivanjem PDF radnji. Idealno z desc = "Preklapa PDF-ove na drugi PDF" title = "Preklapanje PDF-ova" +[home.pdfTextEditor] +title = "Uređivač teksta PDF-a" +desc = "Uređujte postojeći tekst i slike unutar PDF-ova" [home.addText] tags = "tekst,anotacija,oznaka" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Crtani potpis" defaultImageLabel = "Učitani potpis" defaultTextLabel = "Upisani potpis" saveButton = "Spremi potpis" +savePersonal = "Spremi osobno" +saveShared = "Spremi dijeljeno" saveUnavailable = "Najprije izradite potpis da biste ga spremili." noChanges = "Trenutačni potpis je već spremljen." +tempStorageTitle = "Privremena pohrana u pregledniku" +tempStorageDescription = "Potpisi se pohranjuju samo u vašem pregledniku. Izgubit će se ako očistite podatke preglednika ili promijenite preglednik." +personalHeading = "Osobni potpisi" +sharedHeading = "Dijeljeni potpisi" +personalDescription = "Samo vi možete vidjeti ove potpise." +sharedDescription = "Svi korisnici mogu vidjeti i koristiti ove potpise." [sign.saved.type] canvas = "Crtanje" @@ -3020,6 +3036,91 @@ title = "Informacije o PDF-u" header = "Informacije o PDF-u" submit = "Informacije" downloadJson = "Preuzmite JSON" +processing = "Izdvajanje informacija..." +results = "Rezultati" +noResults = "Pokrenite alat za generiranje izvješća." +downloads = "Preuzimanja" +noneDetected = "Ništa nije otkriveno" +indexTitle = "Indeks" + +[getPdfInfo.report] +entryLabel = "Potpuni sažetak informacija" +shortTitle = "Informacije o PDF-u" + +[getPdfInfo.sections] +metadata = "Metapodaci" +formFields = "Polja obrasca" +basicInfo = "Osnovne informacije" +documentInfo = "Informacije o dokumentu" +compliance = "Sukladnost" +encryption = "Šifriranje" +permissions = "Dozvole" +other = "Ostalo" +perPageInfo = "Informacije po stranici" +tableOfContents = "Sadržaj" + +[getPdfInfo.other] +attachments = "Privici" +embeddedFiles = "Ugrađene datoteke" +javaScript = "JavaScript" +layers = "Slojevi" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Veličina" +annotations = "Bilješke" +images = "Slike" +links = "Poveznice" +fonts = "Fontovi" +xobjects = "Broj XObjecta" +multimedia = "Multimedija" + +[getPdfInfo.summary] +pages = "Stranice" +fileSize = "Veličina datoteke" +pdfVersion = "PDF verzija" +language = "Jezik" +title = "Sažetak PDF-a" +author = "Autor" +created = "Stvoreno" +modified = "Izmijenjeno" +permsAll = "Sve dozvole dopuštene" +permsRestricted = "{{count}} ograničenja" +permsMixed = "Neke dozvole su ograničene" +hasCompliance = "Ima standarde sukladnosti" +noCompliance = "Nema standarda sukladnosti" +basic = "Osnovne informacije" +documentInfo = "Informacije o dokumentu" +securityTitle = "Status sigurnosti" +technical = "Tehničko" +overviewTitle = "Pregled PDF-a" + +[getPdfInfo.summary.security] +encrypted = "Šifrirani PDF - prisutna zaštita lozinkom" +unencrypted = "Nešifrirani PDF - bez zaštite lozinkom" + +[getPdfInfo.summary.tech] +images = "Slike" +fonts = "Fontovi" +formFields = "Polja obrasca" +embeddedFiles = "Ugrađene datoteke" +javaScript = "JavaScript" +layers = "Slojevi" +bookmarks = "Oznake" +multimedia = "Multimedija" + +[getPdfInfo.summary.overview] +untitled = "neimenovani dokument" +unknown = "Nepoznat autor" +text = "Ovo je PDF od {{pages}} stranica pod nazivom {{title}}, čiji je autor {{author}} (PDF verzija {{version}})." + +[getPdfInfo.error] +partial = "Neke datoteke nije bilo moguće obraditi." +unexpected = "Neočekivana pogreška tijekom izdvajanja." + +[getPdfInfo.status] +complete = "Izdvajanje dovršeno" [extractPage] tags = "izdvajanje" @@ -3438,6 +3539,9 @@ signinTitle = "Molimo vas da se prijavite" ssoSignIn = "Prijavite se putem jedinstvene prijave" oAuth2AutoCreateDisabled = "OAUTH2 automatsko kreiranje korisnika je onemogućeno" oAuth2AdminBlockedUser = "Registracija ili prijava nekadreguiranih korisnika trenutno su blokirane. Molimo Vas da kontaktirate administratora." +oAuth2RequiresLicense = "Prijava putem OAuth/SSO zahtijeva plaćenu licencu (Server ili Enterprise). Obratite se administratoru radi nadogradnje vašeg plana." +saml2RequiresLicense = "Prijava putem SAML zahtijeva plaćenu licencu (Server ili Enterprise). Obratite se administratoru radi nadogradnje vašeg plana." +maxUsersReached = "Dosegnut je maksimalan broj korisnika za vašu trenutačnu licencu. Obratite se administratoru radi nadogradnje plana ili dodavanja dodatnih mjesta." oauth2RequestNotFound = "Zahtjev za autorizaciju nije pronađen" oauth2InvalidUserInfoResponse = "Nevažeće informacije o korisniku" oauth2invalidRequest = "Neispravan zahtjev" @@ -3846,14 +3950,17 @@ fitToWidth = "Prilagodi širini" actualSize = "Stvarna veličina" [viewer] +cannotPreviewFile = "Nije moguće pregledati datoteku" +dualPageView = "Prikaz dviju stranica" firstPage = "Prva stranica" lastPage = "Zadnja stranica" -previousPage = "Prethodna stranica" nextPage = "Sljedeća stranica" +onlyPdfSupported = "Preglednik podržava samo PDF datoteke. Čini se da je ova datoteka u drugačijem formatu." +previousPage = "Prethodna stranica" +singlePageView = "Prikaz jedne stranice" +unknownFile = "Nepoznata datoteka" zoomIn = "Povećaj" zoomOut = "Umanji" -singlePageView = "Prikaz jedne stranice" -dualPageView = "Prikaz dviju stranica" [rightRail] closeSelected = "Zatvori odabrane datoteke" @@ -3877,6 +3984,7 @@ toggleSidebar = "Prebaci bočnu traku" exportSelected = "Izvezi odabrane stranice" toggleAnnotations = "Prebaci vidljivost bilješki" annotationMode = "Prebaci način bilješki" +print = "Ispis PDF-a" draw = "Crtaj" save = "Spremi" saveChanges = "Spremi promjene" @@ -4494,6 +4602,7 @@ description = "URL ili naziv datoteke za impresum (obvezno u nekim nadležnostim title = "Premium i Enterprise" description = "Konfigurirajte svoj premium ili enterprise licencni ključ." license = "Konfiguracija licence" +noInput = "Molimo navedite licencni ključ ili datoteku" [admin.settings.premium.licenseKey] toggle = "Imate licencni ključ ili datoteku certifikata?" @@ -4511,6 +4620,25 @@ line1 = "Prepisivanje vašeg trenutačnog licencnog ključa ne može se poništi line2 = "Vaša će prethodna licenca trajno biti izgubljena osim ako je niste sigurnosno kopirali drugdje." line3 = "Važno: Licencne ključeve držite privatnima i sigurnima. Nikada ih javno ne dijelite." +[admin.settings.premium.inputMethod] +text = "Licencni ključ" +file = "Datoteka certifikata" + +[admin.settings.premium.file] +label = "Datoteka certifikata licence" +description = "Učitajte svoju .lic ili .cert licencnu datoteku iz izvanmrežnih kupnji" +choose = "Odaberite licencnu datoteku" +selected = "Odabrano: {{filename}} ({{size}})" +successMessage = "Licencna datoteka je uspješno učitana i aktivirana. Nije potrebno ponovno pokretanje." + +[admin.settings.premium.currentLicense] +title = "Aktivna licenca" +file = "Izvor: Licencna datoteka ({{path}})" +key = "Izvor: Licencni ključ" +type = "Vrsta: {{type}}" +noInput = "Navedite licencni ključ ili učitajte datoteku certifikata" +success = "Uspjeh" + [admin.settings.premium.enabled] label = "Omogući premium značajke" description = "Omogući provjere licencnog ključa za pro/enterprise značajke" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} odabrano" download = "Preuzmi datoteku" delete = "Izbriši" unsupported = "Nepodržano" +active = "Aktivno" addToUpload = "Dodaj za otpremu" +closeFile = "Zatvori datoteku" deleteAll = "Izbriši sve" loadingFiles = "Učitavanje datoteka..." noFiles = "Nema dostupnih datoteka" @@ -5132,7 +5262,7 @@ upgrade = "Nadogradi odmah →" freeTitle = "Poslužiteljska licenca" overLimitTitle = "Potrebna poslužiteljska licenca" overLimitBody = "Naše licenciranje dopušta do {{freeTierLimit}} korisnika besplatno po poslužitelju. Imate {{overLimitUserCopy}} Stirling korisnika. Za nesmetan nastavak, nadogradite na Stirling Server plan - neograničena mjesta, uređivanje teksta u PDF-u i puna admin kontrola za $99/server/mo." -freeBody = "Naše Open-Core licenciranje dopušta do {{freeTierLimit}} korisnika besplatno po poslužitelju. Za nesmetano skaliranje i rani pristup našem novom alatu za uređivanje teksta u PDF-u, preporučujemo Stirling Server plan - potpuno uređivanje i neograničena mjesta za $99/server/mo." +freeBody = "Naše licenciranje Open-Core omogućuje do {{freeTierLimit}} korisnika besplatno po poslužitelju. Za neometano skaliranje preporučujemo Stirling Server plan - neograničena mjesta i podrška za SSO za $99/poslužitelj/mj." [onboarding.desktopInstall] title = "Preuzimanje" @@ -5237,6 +5367,31 @@ error = "Nije uspjelo ažuriranje statusa korisnika" success = "Korisnik je uspješno izbrisan" error = "Nije uspjelo brisanje korisnika" +[workspace.people.changePassword] +action = "Promijeni lozinku" +title = "Promijeni lozinku" +subtitle = "Ažuriraj lozinku za" +newPassword = "Nova lozinka" +confirmPassword = "Potvrdi lozinku" +placeholder = "Unesite novu lozinku" +confirmPlaceholder = "Ponovno unesite novu lozinku" +passwordRequired = "Unesite novu lozinku" +passwordMismatch = "Lozinke se ne podudaraju" +generateRandom = "Generiraj sigurnu lozinku" +generatedPreview = "Generirana lozinka:" +copyTooltip = "Kopiraj u međuspremnik" +copiedToClipboard = "Lozinka kopirana u međuspremnik" +copyFailed = "Nije uspjelo kopiranje lozinke" +sendEmail = "Pošalji korisniku e-poruku o ovoj promjeni" +includePassword = "Uključi novu lozinku u e-poruku" +forcePasswordChange = "Prisili korisnika da promijeni lozinku pri sljedećoj prijavi" +emailUnavailable = "E-adresa ovog korisnika nije valjana. Obavijesti su onemogućene." +smtpDisabled = "Obavijesti e-poštom zahtijevaju da SMTP bude omogućen u postavkama." +notifyOnly = "Poslat će se e-poruka bez lozinke kako bi se korisniku javilo da ju je administrator promijenio." +submit = "Ažuriraj lozinku" +success = "Lozinka je uspješno ažurirana" +error = "Nije uspjelo ažuriranje lozinke" + [workspace.people.emailInvite] tab = "Poziv e-poštom" description = "Utipkajte ili zalijepite e-adrese dolje, odvojene zarezima. Korisnici će putem e-pošte dobiti pristupne podatke." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Potreban je barem jedan e-mail" submit = "Pošalji pozive" success = "Korisnici su uspješno pozvani" -partialSuccess = "Neki pozivi nisu uspjeli" +partialFailure = "Neke pozivnice nisu uspjele" allFailed = "Pozivanje korisnika nije uspjelo" error = "Slanje poziva nije uspjelo" @@ -5770,6 +5925,7 @@ subtitle = "Prijavite se svojim Stirling računom" [setup.selfhosted] title = "Prijavite se na poslužitelj" subtitle = "Unesite vjerodajnice poslužitelja" +link = "ili se povežite sa samohostiranim računom" [setup.server] title = "Povežite se s poslužiteljem" @@ -5788,6 +5944,14 @@ description = "Unesite puni URL svog self-hosted Stirling PDF poslužitelja" emptyUrl = "Unesite URL poslužitelja" unreachable = "Nije moguće povezati se s poslužiteljem" testFailed = "Test veze nije uspio" +configFetch = "Neuspjelo dohvaćanje konfiguracije poslužitelja. Provjerite URL i pokušajte ponovno." + +[setup.server.error.securityDisabled] +title = "Prijava nije omogućena" +body = "Na ovom poslužitelju prijava nije omogućena. Da biste se povezali s ovim poslužiteljem, morate omogućiti autentikaciju:" +step1 = "Postavite DOCKER_ENABLE_SECURITY=true u svom okruženju" +step2 = "Ili postavite security.enableLogin=true u settings.yml" +step3 = "Ponovno pokrenite poslužitelj" [setup.login] title = "Prijava" @@ -5797,6 +5961,13 @@ submit = "Prijava" signInWith = "Prijavite se pomoću" oauthPending = "Otvaranje preglednika za autentikaciju..." orContinueWith = "Ili nastavite s e-poštom" +serverRequirement = "Napomena: Poslužitelj mora imati omogućenu prijavu." +showInstructions = "Kako omogućiti?" +hideInstructions = "Sakrij upute" +instructions = "Da biste omogućili prijavu na svom Stirling PDF poslužitelju:" +instructionsEnvVar = "Postavite varijablu okruženja:" +instructionsOrYml = "Ili u settings.yml:" +instructionsRestart = "Zatim ponovno pokrenite poslužitelj kako bi promjene stupile na snagu." [setup.login.username] label = "Korisničko ime" @@ -5840,7 +6011,7 @@ paragraph = "Stranica s odlomcima" sparse = "Rijedak tekst" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automatski" paragraph = "Odlomak" singleLine = "Jedan redak" @@ -5853,6 +6024,7 @@ earlyAccess = "Rani pristup" reset = "Poništi promjene" downloadJson = "Preuzmi JSON" generatePdf = "Generiraj PDF" +saveChanges = "Spremi promjene" [pdfTextEditor.options.autoScaleText] title = "Automatski skaliraj tekst kako bi stao u okvire" @@ -5890,6 +6062,8 @@ alpha = "Ovaj alfa preglednik još se razvija — određeni fontovi, boje, efekt [pdfTextEditor.empty] title = "Nijedan dokument nije učitan" subtitle = "Učitajte PDF ili JSON datoteku kako biste započeli uređivanje teksta." +dropzone = "Ovdje povucite i ispustite PDF ili JSON datoteku ili kliknite za pregledavanje" +dropzoneWithFiles = "Odaberite datoteku s kartice Datoteke ili ovdje povucite i ispustite PDF ili JSON datoteku, ili kliknite za pregledavanje" [pdfTextEditor.welcomeBanner] title = "Dobrodošli u PDF uređivač teksta (rani pristup)" diff --git a/frontend/public/locales/hu-HU/translation.toml b/frontend/public/locales/hu-HU/translation.toml index 514db6f07..d68e3a0bb 100644 --- a/frontend/public/locales/hu-HU/translation.toml +++ b/frontend/public/locales/hu-HU/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Eltávolítás a kedvencekből" fullscreen = "Váltás teljes képernyős módra" sidebar = "Váltás oldalsáv módra" +[backendStartup] +notFoundTitle = "Backend nem található" +retry = "Próbálja újra" +unreachable = "Az alkalmazás jelenleg nem tud csatlakozni a Backendhez. Ellenőrizze a Backend állapotát és a hálózati kapcsolatot, majd próbálja újra." + [zipWarning] title = "Nagy ZIP fájl" message = "Ez a ZIP {{count}} fájlt tartalmaz. Mégis kibontja?" @@ -912,6 +917,9 @@ desc = "Többlépéses munkafolyamatok összeállítása PDF műveletek összef desc = "PDF-ek egymásra helyezése egy másik PDF-en" title = "PDF-ek egymásra helyezése" +[home.pdfTextEditor] +title = "PDF szövegszerkesztő" +desc = "Meglévő szöveg és képek szerkesztése a PDF-ekben" [home.addText] tags = "szöveg, megjegyzés, címke" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Rajzolt aláírás" defaultImageLabel = "Feltöltött aláírás" defaultTextLabel = "Gépelt aláírás" saveButton = "Aláírás mentése" +savePersonal = "Mentés személyesként" +saveShared = "Mentés megosztottként" saveUnavailable = "Előbb hozzon létre egy aláírást a mentéshez." noChanges = "Az aktuális aláírás már mentve van." +tempStorageTitle = "Ideiglenes böngészőbeli tárolás" +tempStorageDescription = "Az aláírások csak a böngészőben tárolódnak. Elvesznek, ha törli a böngészőadatokat vagy böngészőt vált." +personalHeading = "Személyes aláírások" +sharedHeading = "Megosztott aláírások" +personalDescription = "Csak Ön láthatja ezeket az aláírásokat." +sharedDescription = "Minden felhasználó láthatja és használhatja ezeket az aláírásokat." [sign.saved.type] canvas = "Rajz" @@ -3020,6 +3036,91 @@ title = "PDF információk lekérése" header = "PDF információk lekérése" submit = "Információk lekérése" downloadJson = "JSON letöltése" +processing = "Információk kinyerése..." +results = "Eredmények" +noResults = "Futtassa az eszközt a jelentés létrehozásához." +downloads = "Letöltések" +noneDetected = "Nem található" +indexTitle = "Index" + +[getPdfInfo.report] +entryLabel = "Teljes információs összefoglaló" +shortTitle = "PDF-információk" + +[getPdfInfo.sections] +metadata = "Metaadatok" +formFields = "Űrlapmezők" +basicInfo = "Alapinformációk" +documentInfo = "Dokumentuminformációk" +compliance = "Megfelelőség" +encryption = "Titkosítás" +permissions = "Engedélyek" +other = "Egyéb" +perPageInfo = "Oldalankénti információk" +tableOfContents = "Tartalomjegyzék" + +[getPdfInfo.other] +attachments = "Mellékletek" +embeddedFiles = "Beágyazott fájlok" +javaScript = "JavaScript" +layers = "Rétegek" +structureTree = "Struktúrafa" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Méret" +annotations = "Megjegyzések" +images = "Képek" +links = "Hivatkozások" +fonts = "Betűtípusok" +xobjects = "XObject-ek száma" +multimedia = "Multimédia" + +[getPdfInfo.summary] +pages = "Oldalak" +fileSize = "Fájlméret" +pdfVersion = "PDF-verzió" +language = "Nyelv" +title = "PDF-összefoglaló" +author = "Szerző" +created = "Létrehozva" +modified = "Módosítva" +permsAll = "Minden jogosultság engedélyezve" +permsRestricted = "{{count}} korlátozás" +permsMixed = "Néhány jogosultság korlátozott" +hasCompliance = "Megfelelőségi szabványokkal rendelkezik" +noCompliance = "Nincsenek megfelelőségi szabványok" +basic = "Alapinformációk" +documentInfo = "Dokumentuminformációk" +securityTitle = "Biztonsági állapot" +technical = "Technikai" +overviewTitle = "PDF-áttekintés" + +[getPdfInfo.summary.security] +encrypted = "Titkosított PDF – jelszóvédelemmel" +unencrypted = "Titkosítatlan PDF – nincs jelszóvédelem" + +[getPdfInfo.summary.tech] +images = "Képek" +fonts = "Betűtípusok" +formFields = "Űrlapmezők" +embeddedFiles = "Beágyazott fájlok" +javaScript = "JavaScript" +layers = "Rétegek" +bookmarks = "Könyvjelzők" +multimedia = "Multimédia" + +[getPdfInfo.summary.overview] +untitled = "egy cím nélküli dokumentum" +unknown = "Ismeretlen szerző" +text = "Ez egy {{pages}} oldalas, {{title}} című PDF, amelyet {{author}} készített (PDF-verzió: {{version}})." + +[getPdfInfo.error] +partial = "Néhány fájlt nem sikerült feldolgozni." +unexpected = "Váratlan hiba a kinyerés során." + +[getPdfInfo.status] +complete = "Kinyerés befejezve" [extractPage] tags = "kinyerés" @@ -3438,6 +3539,9 @@ signinTitle = "Kérjük, jelentkezzen be" ssoSignIn = "Bejelentkezés egyszeri bejelentkezéssel" oAuth2AutoCreateDisabled = "OAuth2 automatikus felhasználólétrehozás letiltva" oAuth2AdminBlockedUser = "A nem regisztrált felhasználók regisztrációja vagy bejelentkezése jelenleg le van tiltva. Kérjük, forduljon a rendszergazdához." +oAuth2RequiresLicense = "Az OAuth/SSO bejelentkezés fizetős licencet igényel (Server vagy Enterprise). Kérjük, lépjen kapcsolatba az adminisztrátorral a csomag frissítéséhez." +saml2RequiresLicense = "A SAML bejelentkezés fizetős licencet igényel (Server vagy Enterprise). Kérjük, lépjen kapcsolatba az adminisztrátorral a csomag frissítéséhez." +maxUsersReached = "Elérte az aktuális licenchez tartozó felhasználók maximális számát. Kérjük, lépjen kapcsolatba az adminisztrátorral a csomag frissítéséhez vagy további felhasználói helyek hozzáadásához." oauth2RequestNotFound = "A hitelesítési kérés nem található" oauth2InvalidUserInfoResponse = "Érvénytelen felhasználói információ válasz" oauth2invalidRequest = "Érvénytelen kérés" @@ -3846,14 +3950,17 @@ fitToWidth = "Szélességhez igazítás" actualSize = "Tényleges méret" [viewer] +cannotPreviewFile = "A fájl előnézete nem lehetséges" +dualPageView = "Kétoldalas nézet" firstPage = "Első oldal" lastPage = "Utolsó oldal" -previousPage = "Előző oldal" nextPage = "Következő oldal" +onlyPdfSupported = "A megjelenítő csak PDF fájlokat támogat. Úgy tűnik, ez a fájl más formátumú." +previousPage = "Előző oldal" +singlePageView = "Egyoldalas nézet" +unknownFile = "Ismeretlen fájl" zoomIn = "Nagyítás" zoomOut = "Kicsinyítés" -singlePageView = "Egyoldalas nézet" -dualPageView = "Kétoldalas nézet" [rightRail] closeSelected = "Kijelölt fájlok bezárása" @@ -3877,6 +3984,7 @@ toggleSidebar = "Oldalsáv ki/be" exportSelected = "Kijelölt oldalak exportálása" toggleAnnotations = "Jegyzetek láthatóságának váltása" annotationMode = "Jegyzetelési mód váltása" +print = "PDF nyomtatása" draw = "Rajzolás" save = "Mentés" saveChanges = "Változtatások mentése" @@ -4494,6 +4602,7 @@ description = "URL vagy fájlnév az impresszumhoz (egyes joghatóságokban köt title = "Prémium és Vállalati" description = "Prémium vagy vállalati licenckulcs konfigurálása." license = "Licenckonfiguráció" +noInput = "Kérjük, adjon meg egy licenckulcsot vagy fájlt" [admin.settings.premium.licenseKey] toggle = "Van licenckulcsa vagy tanúsítványfájlja?" @@ -4511,6 +4620,25 @@ line1 = "A jelenlegi licenckulcs felülírása nem vonható vissza." line2 = "A korábbi licenc végleg elveszik, hacsak nem készített róla máshol biztonsági másolatot." line3 = "Fontos: Tartsa a licenckulcsokat bizalmasan és biztonságban. Soha ne ossza meg nyilvánosan." +[admin.settings.premium.inputMethod] +text = "Licenckulcs" +file = "Tanúsítványfájl" + +[admin.settings.premium.file] +label = "Licenc tanúsítványfájl" +description = "Töltse fel az offline vásárlásból származó .lic vagy .cert licencfájlt" +choose = "Licencfájl kiválasztása" +selected = "Kiválasztva: {{filename}} ({{size}})" +successMessage = "A licencfájl feltöltése és aktiválása sikeres. Nincs szükség újraindításra." + +[admin.settings.premium.currentLicense] +title = "Aktív licenc" +file = "Forrás: licencfájl ({{path}})" +key = "Forrás: licenckulcs" +type = "Típus: {{type}}" +noInput = "Adjon meg egy licenckulcsot, vagy töltsön fel tanúsítványfájlt" +success = "Siker" + [admin.settings.premium.enabled] label = "Prémium funkciók engedélyezése" description = "Licenckulcs-ellenőrzések engedélyezése a pro/vállalati funkciókhoz" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} kiválasztva" download = "Letöltés" delete = "Törlés" unsupported = "Nem támogatott" +active = "Aktív" addToUpload = "Hozzáadás a feltöltéshez" +closeFile = "Fájl bezárása" deleteAll = "Összes törlése" loadingFiles = "Fájlok betöltése..." noFiles = "Nem állnak rendelkezésre fájlok" @@ -5132,7 +5262,7 @@ upgrade = "Frissítés most →" freeTitle = "Szerverlicenc" overLimitTitle = "Szerverlicenc szükséges" overLimitBody = "Licencelésünk szerverenként legfeljebb {{freeTierLimit}} felhasználót enged ingyen. Önnek {{overLimitUserCopy}} Stirling felhasználója van. A zavartalan használathoz váltson a Stirling Server csomagra – korlátlan hely, PDF szövegszerkesztés és teljes adminisztrátori vezérlés $99/szerver/hó áron." -freeBody = "Az Open-Core licencelésünk szerverenként legfeljebb {{freeTierLimit}} felhasználót enged ingyen. A zavartalan bővüléshez és az új PDF szövegszerkesztő eszköz korai eléréséhez a Stirling Server csomagot ajánljuk – teljes szerkesztés és korlátlan hely $99/szerver/hó áron." +freeBody = "A Open-Core licencünk szerverenként legfeljebb {{freeTierLimit}} felhasználót engedélyez ingyenesen. A zökkenőmentes skálázáshoz a Stirling Server csomagot ajánljuk - korlátlan felhasználó és SSO támogatás $99/szerver/hó." [onboarding.desktopInstall] title = "Letöltés" @@ -5237,6 +5367,31 @@ error = "Nem sikerült frissíteni a felhasználói állapotot" success = "Felhasználó sikeresen törölve" error = "Nem sikerült törölni a felhasználót" +[workspace.people.changePassword] +action = "Jelszó módosítása" +title = "Jelszó módosítása" +subtitle = "Jelszó frissítése ehhez:" +newPassword = "Új jelszó" +confirmPassword = "Jelszó megerősítése" +placeholder = "Adjon meg egy új jelszót" +confirmPlaceholder = "Adja meg újra az új jelszót" +passwordRequired = "Kérjük, adjon meg egy új jelszót" +passwordMismatch = "A jelszavak nem egyeznek" +generateRandom = "Biztonságos jelszó generálása" +generatedPreview = "Generált jelszó:" +copyTooltip = "Másolás a vágólapra" +copiedToClipboard = "A jelszó a vágólapra másolva" +copyFailed = "A jelszó másolása nem sikerült" +sendEmail = "E-mail küldése a felhasználónak a változásról" +includePassword = "Az új jelszó szerepeljen az e-mailben" +forcePasswordChange = "A felhasználó kényszerítése a jelszó megváltoztatására a következő bejelentkezéskor" +emailUnavailable = "Ennek a felhasználónak az e-mail címe érvénytelen. Az értesítések le vannak tiltva." +smtpDisabled = "Az e-mail értesítésekhez az SMTP engedélyezése szükséges a beállításokban." +notifyOnly = "E-mailt küldünk jelszó nélkül, amelyben értesítjük a felhasználót, hogy egy admin módosította a jelszót." +submit = "Jelszó frissítése" +success = "A jelszó sikeresen frissítve" +error = "A jelszó frissítése nem sikerült" + [workspace.people.emailInvite] tab = "E-mail meghívó" description = "Írja be vagy illessze be alább az e-mail címeket, vesszővel elválasztva. A felhasználók e-mailben kapják meg a bejelentkezési adatokat." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Legalább egy e-mail cím megadása szükséges" submit = "Meghívók küldése" success = "felhasználó sikeresen meghívva" -partialSuccess = "Néhány meghívó sikertelen volt" +partialFailure = "Néhány meghívás sikertelen volt" allFailed = "Nem sikerült meghívni a felhasználókat" error = "Nem sikerült elküldeni a meghívókat" @@ -5770,6 +5925,7 @@ subtitle = "Jelentkezzen be Stirling-fiókjával" [setup.selfhosted] title = "Bejelentkezés a szerverre" subtitle = "Adja meg a szerver hitelesítő adatait" +link = "vagy csatlakozzon egy saját üzemeltetésű fiókhoz" [setup.server] title = "Csatlakozás a szerverhez" @@ -5788,6 +5944,14 @@ description = "Adja meg az önhostolt Stirling PDF szerver teljes URL-jét" emptyUrl = "Adjon meg egy szerver URL-t" unreachable = "Nem sikerült kapcsolódni a szerverhez" testFailed = "A kapcsolat tesztje sikertelen" +configFetch = "Nem sikerült letölteni a szerver konfigurációját. Ellenőrizze az URL-t, és próbálja meg újra." + +[setup.server.error.securityDisabled] +title = "A bejelentkezés nincs engedélyezve" +body = "Ezen a szerveren a bejelentkezés nincs engedélyezve. A csatlakozáshoz engedélyeznie kell a hitelesítést:" +step1 = "Állítsa be a DOCKER_ENABLE_SECURITY=true értéket a környezetében" +step2 = "Vagy állítsa be a security.enableLogin=true értéket a settings.yml fájlban" +step3 = "Indítsa újra a szervert" [setup.login] title = "Bejelentkezés" @@ -5797,6 +5961,13 @@ submit = "Bejelentkezés" signInWith = "Bejelentkezés ezzel" oauthPending = "Böngésző megnyitása hitelesítéshez..." orContinueWith = "Vagy folytassa e-maillel" +serverRequirement = "Megjegyzés: A szerveren engedélyezni kell a bejelentkezést." +showInstructions = "Hogyan engedélyezhető?" +hideInstructions = "Utasítások elrejtése" +instructions = "A bejelentkezés engedélyezéséhez a Stirling PDF szerverén:" +instructionsEnvVar = "Állítsa be a környezeti változót:" +instructionsOrYml = "Vagy a settings.yml-ben:" +instructionsRestart = "Ezután indítsa újra a szervert, hogy a módosítások életbe lépjenek." [setup.login.username] label = "Felhasználónév" @@ -5853,6 +6024,7 @@ earlyAccess = "Korai hozzáférés" reset = "Módosítások visszaállítása" downloadJson = "JSON letöltése" generatePdf = "PDF generálása" +saveChanges = "Változtatások mentése" [pdfTextEditor.options.autoScaleText] title = "Szöveg automatikus méretezése a dobozokhoz" @@ -5890,6 +6062,8 @@ alpha = "Ez az alfa néző még fejlődik—bizonyos betűtípusok, színek, át [pdfTextEditor.empty] title = "Nincs dokumentum betöltve" subtitle = "Töltsön be egy PDF- vagy JSON-fájlt a szövegtartalom szerkesztésének megkezdéséhez." +dropzone = "Húzzon ide egy PDF vagy JSON fájlt, vagy kattintson a tallózáshoz" +dropzoneWithFiles = "Válasszon fájlt a Fájlok fülön, vagy húzzon ide egy PDF vagy JSON fájlt, illetve kattintson a tallózáshoz" [pdfTextEditor.welcomeBanner] title = "Üdvözöljük a PDF Text Editorben (korai hozzáférés)" diff --git a/frontend/public/locales/id-ID/translation.toml b/frontend/public/locales/id-ID/translation.toml index 010266296..77758a492 100644 --- a/frontend/public/locales/id-ID/translation.toml +++ b/frontend/public/locales/id-ID/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Hapus dari Favorit" fullscreen = "Beralih ke mode layar penuh" sidebar = "Beralih ke mode bilah sisi" +[backendStartup] +notFoundTitle = "Backend tidak ditemukan" +retry = "Coba lagi" +unreachable = "Aplikasi saat ini tidak dapat terhubung ke backend. Periksa status backend dan konektivitas jaringan, lalu coba lagi." + [zipWarning] title = "File ZIP Besar" message = "ZIP ini berisi {{count}} file. Tetap ekstrak?" @@ -190,7 +195,7 @@ title = "Pengaturan Dibuka" message = "Silakan pilih Stirling PDF di pengaturan sistem Anda" [defaultApp.error] -title = "Error" +title = "Kesalahan" message = "Gagal menyetel penangan PDF default" [language] @@ -348,7 +353,7 @@ title = "Konfigurasi" systemSettings = "Pengaturan Sistem" features = "Fitur" endpoints = "Endpoint" -database = "Database" +database = "Basis Data" advanced = "Lanjutan" [settings.securityAuth] @@ -383,7 +388,7 @@ logout = "Keluar" [settings.connection.mode] saas = "Stirling Cloud" -selfhosted = "Self-Hosted" +selfhosted = "Dihost Sendiri" [settings.general] title = "Umum" @@ -544,8 +549,8 @@ usage = "Lihat Penggunaan" [endpointStatistics] title = "Statistik Endpoint" header = "Statistik Endpoint" -top10 = "Top 10" -top20 = "Top 20" +top10 = "10 Teratas" +top20 = "20 Teratas" all = "Semua" refresh = "Muat Ulang" dataTypeLabel = "Tipe Data:" @@ -912,6 +917,9 @@ desc = "Bangun alur kerja multi-langkah dengan merangkai tindakan PDF. Ideal unt desc = "Menumpuk PDF di atas PDF lain" title = "Tumpuk PDF" +[home.pdfTextEditor] +title = "Editor Teks PDF" +desc = "Edit teks dan gambar yang ada di dalam PDF" [home.addText] tags = "teks,anotasi,label" @@ -1173,7 +1181,7 @@ selectFilesPlaceholder = "Pilih file di tampilan utama untuk memulai" settings = "Pengaturan" conversionCompleted = "Konversi selesai" results = "Hasil" -defaultFilename = "converted_file" +defaultFilename = "file_terkonversi" conversionResults = "Hasil Konversi" convertFrom = "Konversi dari" convertTo = "Konversi ke" @@ -1360,7 +1368,7 @@ title = "Tambahkan Watermark" desc = "Tambahkan tanda air teks atau gambar ke file PDF" completed = "Tanda air ditambahkan" submit = "Tambahkan Watermark" -filenamePrefix = "watermarked" +filenamePrefix = "bertanda_air" [watermark.error] failed = "Terjadi kesalahan saat menambahkan tanda air ke PDF." @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Tanda tangan gambar" defaultImageLabel = "Tanda tangan terunggah" defaultTextLabel = "Tanda tangan ketik" saveButton = "Simpan tanda tangan" +savePersonal = "Simpan Pribadi" +saveShared = "Simpan Bersama" saveUnavailable = "Buat tanda tangan terlebih dahulu untuk menyimpannya." noChanges = "Tanda tangan saat ini sudah disimpan." +tempStorageTitle = "Penyimpanan browser sementara" +tempStorageDescription = "Tanda tangan disimpan hanya di browser Anda. Data akan hilang jika Anda membersihkan data browser atau berpindah browser." +personalHeading = "Tanda Tangan Pribadi" +sharedHeading = "Tanda Tangan Bersama" +personalDescription = "Hanya Anda yang dapat melihat tanda tangan ini." +sharedDescription = "Semua pengguna dapat melihat dan menggunakan tanda tangan ini." [sign.saved.type] canvas = "Gambar" @@ -2701,7 +2717,7 @@ header = "Hapus sertifikat digital dari PDF" selectPDF = "Pilih file PDF:" submit = "Hapus Tanda Tangan" description = "Alat ini akan menghapus tanda tangan sertifikat digital dari dokumen PDF Anda." -filenamePrefix = "unsigned" +filenamePrefix = "tanpa_tanda_tangan" [removeCertSign.files] placeholder = "Pilih file PDF di tampilan utama untuk memulai" @@ -3020,6 +3036,91 @@ title = "Dapatkan Info tentang PDF" header = "Dapatkan Info tentang PDF" submit = "Dapatkan Info" downloadJson = "Unduh JSON" +processing = "Mengekstrak informasi..." +results = "Hasil" +noResults = "Jalankan alat untuk menghasilkan laporan." +downloads = "Unduhan" +noneDetected = "Tidak ada yang terdeteksi" +indexTitle = "Indeks" + +[getPdfInfo.report] +entryLabel = "Ringkasan informasi lengkap" +shortTitle = "Informasi PDF" + +[getPdfInfo.sections] +metadata = "Metadata" +formFields = "Bidang Formulir" +basicInfo = "Info Dasar" +documentInfo = "Info Dokumen" +compliance = "Kepatuhan" +encryption = "Enkripsi" +permissions = "Izin" +other = "Lainnya" +perPageInfo = "Info per Halaman" +tableOfContents = "Daftar Isi" + +[getPdfInfo.other] +attachments = "Lampiran" +embeddedFiles = "File Tertanam" +javaScript = "JavaScript" +layers = "Lapisan" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Ukuran" +annotations = "Anotasi" +images = "Gambar" +links = "Tautan" +fonts = "Font" +xobjects = "Jumlah XObject" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Halaman" +fileSize = "Ukuran File" +pdfVersion = "Versi PDF" +language = "Bahasa" +title = "Ringkasan PDF" +author = "Penulis" +created = "Dibuat" +modified = "Diubah" +permsAll = "Semua izin diperbolehkan" +permsRestricted = "{{count}} pembatasan" +permsMixed = "Beberapa izin dibatasi" +hasCompliance = "Memiliki standar kepatuhan" +noCompliance = "Tidak ada standar kepatuhan" +basic = "Informasi Dasar" +documentInfo = "Informasi Dokumen" +securityTitle = "Status Keamanan" +technical = "Teknis" +overviewTitle = "Gambaran Umum PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF terenkripsi - Perlindungan kata sandi aktif" +unencrypted = "PDF tidak terenkripsi - Tanpa perlindungan kata sandi" + +[getPdfInfo.summary.tech] +images = "Gambar" +fonts = "Font" +formFields = "Bidang Formulir" +embeddedFiles = "File Tertanam" +javaScript = "JavaScript" +layers = "Lapisan" +bookmarks = "Bookmark" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "dokumen tanpa judul" +unknown = "Penulis tidak diketahui" +text = "Ini adalah PDF {{pages}} halaman berjudul {{title}} yang dibuat oleh {{author}} (versi PDF {{version}})." + +[getPdfInfo.error] +partial = "Beberapa file tidak dapat diproses." +unexpected = "Kesalahan tak terduga saat ekstraksi." + +[getPdfInfo.status] +complete = "Ekstraksi selesai" [extractPage] tags = "ekstrak" @@ -3438,6 +3539,9 @@ signinTitle = "Silakan masuk" ssoSignIn = "Masuk melalui Single Sign - on" oAuth2AutoCreateDisabled = "OAUTH2 Buat Otomatis Pengguna Dinonaktifkan" oAuth2AdminBlockedUser = "Registrasi atau login pengguna yang tidak terdaftar saat ini diblokir. Silakan hubungi administrator." +oAuth2RequiresLicense = "Login OAuth/SSO memerlukan lisensi berbayar (Server atau Enterprise). Silakan hubungi administrator untuk meningkatkan paket Anda." +saml2RequiresLicense = "Login SAML memerlukan lisensi berbayar (Server atau Enterprise). Silakan hubungi administrator untuk meningkatkan paket Anda." +maxUsersReached = "Jumlah pengguna maksimum untuk lisensi Anda saat ini telah tercapai. Silakan hubungi administrator untuk meningkatkan paket Anda atau menambah seat." oauth2RequestNotFound = "Permintaan otorisasi tidak ditemukan" oauth2InvalidUserInfoResponse = "Respons Info Pengguna Tidak Valid" oauth2invalidRequest = "Permintaan Tidak Valid" @@ -3533,7 +3637,7 @@ title = "PDF Ke Halaman Tunggal" header = "PDF Ke Halaman Tunggal" submit = "Konversi ke Halaman Tunggal" description = "Alat ini akan menggabungkan semua halaman PDF Anda menjadi satu halaman besar. Lebarnya akan tetap sama dengan halaman asli, tetapi tingginya merupakan penjumlahan dari semua tinggi halaman." -filenamePrefix = "single_page" +filenamePrefix = "halaman_tunggal" [pdfToSinglePage.files] placeholder = "Pilih file PDF di tampilan utama untuk memulai" @@ -3846,14 +3950,17 @@ fitToWidth = "Sesuaikan ke Lebar" actualSize = "Ukuran Asli" [viewer] +cannotPreviewFile = "Tidak dapat menampilkan pratinjau file" +dualPageView = "Tampilan Dua Halaman" firstPage = "Halaman Pertama" lastPage = "Halaman Terakhir" -previousPage = "Halaman Sebelumnya" nextPage = "Halaman Berikutnya" +onlyPdfSupported = "Penampil hanya mendukung file PDF. File ini tampaknya memiliki format yang berbeda." +previousPage = "Halaman Sebelumnya" +singlePageView = "Tampilan Satu Halaman" +unknownFile = "File tidak dikenal" zoomIn = "Perbesar" zoomOut = "Perkecil" -singlePageView = "Tampilan Satu Halaman" -dualPageView = "Tampilan Dua Halaman" [rightRail] closeSelected = "Tutup File Terpilih" @@ -3877,6 +3984,7 @@ toggleSidebar = "Alihkan Sidebar" exportSelected = "Ekspor Halaman Terpilih" toggleAnnotations = "Alihkan Visibilitas Anotasi" annotationMode = "Alihkan Mode Anotasi" +print = "Cetak PDF" draw = "Gambar" save = "Simpan" saveChanges = "Simpan Perubahan" @@ -4282,7 +4390,7 @@ label = "Blokir Pendaftaran" description = "Cegah pendaftaran pengguna baru melalui SAML2" [admin.settings.database] -title = "Database" +title = "Basis Data" description = "Konfigurasikan pengaturan koneksi database kustom untuk penerapan enterprise." configuration = "Konfigurasi Database" @@ -4494,6 +4602,7 @@ description = "URL atau nama file untuk impressum (diperlukan di beberapa yurisd title = "Premium & Enterprise" description = "Konfigurasikan kunci lisensi premium atau enterprise Anda." license = "Konfigurasi Lisensi" +noInput = "Harap berikan kunci atau file lisensi" [admin.settings.premium.licenseKey] toggle = "Punya kunci lisensi atau file sertifikat?" @@ -4511,6 +4620,25 @@ line1 = "Menimpa kunci lisensi Anda saat ini tidak dapat dibatalkan." line2 = "Lisensi sebelumnya akan hilang permanen kecuali Anda mencadangkannya di tempat lain." line3 = "Penting: Jaga kunci lisensi tetap privat dan aman. Jangan pernah membagikannya secara publik." +[admin.settings.premium.inputMethod] +text = "Kunci Lisensi" +file = "File Sertifikat" + +[admin.settings.premium.file] +label = "File Sertifikat Lisensi" +description = "Unggah file lisensi .lic atau .cert Anda dari pembelian offline" +choose = "Pilih File Lisensi" +selected = "Dipilih: {{filename}} ({{size}})" +successMessage = "File lisensi berhasil diunggah dan diaktifkan. Tidak perlu restart." + +[admin.settings.premium.currentLicense] +title = "Lisensi Aktif" +file = "Sumber: File lisensi ({{path}})" +key = "Sumber: Kunci lisensi" +type = "Tipe: {{type}}" +noInput = "Harap berikan kunci lisensi atau unggah file sertifikat" +success = "Berhasil" + [admin.settings.premium.enabled] label = "Aktifkan Fitur Premium" description = "Aktifkan pemeriksaan kunci lisensi untuk fitur pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} dipilih" download = "Unduh" delete = "Hapus" unsupported = "Tidak didukung" +active = "Aktif" addToUpload = "Tambahkan ke Unggahan" +closeFile = "Tutup File" deleteAll = "Hapus Semua" loadingFiles = "Memuat file..." noFiles = "Tidak ada file tersedia" @@ -4938,13 +5068,13 @@ done = "Selesai" loading = "Memuat..." back = "Kembali" continue = "Lanjut" -error = "Error" +error = "Kesalahan" [config.overview] title = "Konfigurasi Aplikasi" description = "Pengaturan dan detail konfigurasi aplikasi saat ini." loading = "Memuat konfigurasi..." -error = "Error" +error = "Kesalahan" warning = "Peringatan Konfigurasi" [config.overview.sections] @@ -5132,7 +5262,7 @@ upgrade = "Upgrade sekarang →" freeTitle = "Lisensi Server" overLimitTitle = "Perlu Lisensi Server" overLimitBody = "Lisensi kami mengizinkan hingga {{freeTierLimit}} pengguna gratis per server. Anda memiliki {{overLimitUserCopy}} pengguna Stirling. Untuk terus berjalan tanpa gangguan, upgrade ke paket Stirling Server - kursi tanpa batas, pengeditan teks PDF, dan kontrol admin penuh seharga $99/server/bulan." -freeBody = "Lisensi Open-Core kami mengizinkan hingga {{freeTierLimit}} pengguna gratis per server. Untuk skala tanpa hambatan dan mendapatkan akses awal ke alat pengeditan teks PDF baru kami, kami sarankan paket Stirling Server - pengeditan penuh dan kursi tanpa batas seharga $99/server/bulan." +freeBody = "Lisensi Open-Core kami mengizinkan hingga {{freeTierLimit}} pengguna gratis per server. Untuk meningkatkan skala tanpa gangguan, kami merekomendasikan paket Stirling Server - pengguna tanpa batas dan dukungan SSO seharga $99/server/mo." [onboarding.desktopInstall] title = "Unduh" @@ -5237,6 +5367,31 @@ error = "Gagal memperbarui status pengguna" success = "Pengguna berhasil dihapus" error = "Gagal menghapus pengguna" +[workspace.people.changePassword] +action = "Ubah kata sandi" +title = "Ubah kata sandi" +subtitle = "Perbarui kata sandi untuk" +newPassword = "Kata sandi baru" +confirmPassword = "Konfirmasi kata sandi" +placeholder = "Masukkan kata sandi baru" +confirmPlaceholder = "Masukkan ulang kata sandi baru" +passwordRequired = "Silakan masukkan kata sandi baru" +passwordMismatch = "Kata sandi tidak cocok" +generateRandom = "Buat kata sandi aman" +generatedPreview = "Kata sandi yang dibuat:" +copyTooltip = "Salin ke papan klip" +copiedToClipboard = "Kata sandi disalin ke papan klip" +copyFailed = "Gagal menyalin kata sandi" +sendEmail = "Kirim email kepada pengguna tentang perubahan ini" +includePassword = "Sertakan kata sandi baru dalam email" +forcePasswordChange = "Paksa pengguna mengganti kata sandi saat login berikutnya" +emailUnavailable = "Email pengguna ini bukan alamat email yang valid. Notifikasi dinonaktifkan." +smtpDisabled = "Notifikasi email memerlukan SMTP diaktifkan di pengaturan." +notifyOnly = "Email akan dikirim tanpa kata sandi, memberi tahu pengguna bahwa admin telah mengubahnya." +submit = "Perbarui kata sandi" +success = "Kata sandi berhasil diperbarui" +error = "Gagal memperbarui kata sandi" + [workspace.people.emailInvite] tab = "Undangan Email" description = "Ketik atau tempel email di bawah, dipisahkan dengan koma. Pengguna akan menerima kredensial login melalui email." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Setidaknya satu alamat email diperlukan" submit = "Kirim Undangan" success = "pengguna berhasil diundang" -partialSuccess = "Beberapa undangan gagal" +partialFailure = "Beberapa undangan gagal" allFailed = "Gagal mengundang pengguna" error = "Gagal mengirim undangan" @@ -5388,7 +5543,7 @@ hideComparison = "Sembunyikan Perbandingan Fitur" featureComparison = "Perbandingan Fitur" from = "Mulai" perMonth = "/bulan" -perSeat = "/seat" +perSeat = "/pengguna" withServer = "+ Paket Server" licensedSeats = "Berlisensi: {{count}} seat" includedInCurrent = "Termasuk dalam Paket Anda" @@ -5549,7 +5704,7 @@ modalTitle = "Mulai - {{planName}}" title = "Pilih Periode Penagihan" savingsNote = "Hemat {{percent}}% dengan penagihan tahunan" basePrice = "Harga Dasar" -seatPrice = "Per Seat" +seatPrice = "Per Pengguna" totalForSeats = "Total ({{count}} seat)" selectMonthly = "Pilih Bulanan" selectYearly = "Pilih Tahunan" @@ -5752,7 +5907,7 @@ label = "Pilih Server" description = "Server self-hosted" [setup.step3] -label = "Login" +label = "Masuk" description = "Masukkan kredensial" [setup.mode.saas] @@ -5770,6 +5925,7 @@ subtitle = "Masuk dengan akun Stirling Anda" [setup.selfhosted] title = "Masuk ke Server" subtitle = "Masukkan kredensial server Anda" +link = "atau hubungkan ke akun self-hosted" [setup.server] title = "Sambungkan ke Server" @@ -5788,15 +5944,30 @@ description = "Masukkan URL lengkap server Stirling PDF self-hosted Anda" emptyUrl = "Masukkan URL server" unreachable = "Tidak dapat terhubung ke server" testFailed = "Tes koneksi gagal" +configFetch = "Gagal mengambil konfigurasi server. Periksa URL dan coba lagi." + +[setup.server.error.securityDisabled] +title = "Login Tidak Diaktifkan" +body = "Server ini tidak mengaktifkan login. Untuk terhubung ke server ini, Anda harus mengaktifkan autentikasi:" +step1 = "Setel DOCKER_ENABLE_SECURITY=true di lingkungan Anda" +step2 = "Atau setel security.enableLogin=true di settings.yml" +step3 = "Mulai ulang server" [setup.login] title = "Masuk" subtitle = "Masukkan kredensial Anda untuk melanjutkan" connectingTo = "Menghubungkan ke:" -submit = "Login" +submit = "Masuk" signInWith = "Masuk dengan" oauthPending = "Membuka browser untuk autentikasi..." orContinueWith = "Atau lanjut dengan email" +serverRequirement = "Catatan: Server harus mengaktifkan login." +showInstructions = "Bagaimana cara mengaktifkannya?" +hideInstructions = "Sembunyikan instruksi" +instructions = "Untuk mengaktifkan login pada server Stirling PDF Anda:" +instructionsEnvVar = "Setel variabel lingkungan:" +instructionsOrYml = "Atau di settings.yml:" +instructionsRestart = "Kemudian mulai ulang server Anda agar perubahan diterapkan." [setup.login.username] label = "Nama pengguna" @@ -5853,6 +6024,7 @@ earlyAccess = "Akses Awal" reset = "Reset Perubahan" downloadJson = "Unduh JSON" generatePdf = "Buat PDF" +saveChanges = "Simpan Perubahan" [pdfTextEditor.options.autoScaleText] title = "Sesuaikan teks otomatis ke kotak" @@ -5890,6 +6062,8 @@ alpha = "Penampil alpha ini masih berkembang—beberapa font, warna, efek transp [pdfTextEditor.empty] title = "Tidak ada dokumen dimuat" subtitle = "Muat file PDF atau JSON untuk mulai mengedit konten teks." +dropzone = "Seret dan letakkan file PDF atau JSON di sini, atau klik untuk memilih" +dropzoneWithFiles = "Pilih file dari tab File, atau seret dan letakkan file PDF atau JSON di sini, atau klik untuk memilih" [pdfTextEditor.welcomeBanner] title = "Selamat datang di PDF Text Editor (Akses Awal)" diff --git a/frontend/public/locales/it-IT/translation.toml b/frontend/public/locales/it-IT/translation.toml index ef9d94d83..20796ceb0 100644 --- a/frontend/public/locales/it-IT/translation.toml +++ b/frontend/public/locales/it-IT/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Rimuovi dai preferiti" fullscreen = "Passa alla modalità a schermo intero" sidebar = "Passa alla modalità barra laterale" +[backendStartup] +notFoundTitle = "Backend non trovato" +retry = "Riprova" +unreachable = "L'applicazione al momento non riesce a connettersi al backend. Verificare lo stato del backend e la connettività di rete, quindi riprovare." + [zipWarning] title = "File ZIP di grandi dimensioni" message = "Questo ZIP contiene {{count}} file. Estrarre comunque?" @@ -339,7 +344,7 @@ popular = "Popolare" title = "Preferenze" [settings.workspace] -title = "Workspace" +title = "Area di lavoro" people = "Persone" teams = "Team" @@ -912,6 +917,9 @@ desc = "Crea flussi multi‑step concatenando azioni PDF. Ideale per attività r desc = "Sovrapponi un PDF sopra un altro" title = "Sovrapponi PDF" +[home.pdfTextEditor] +title = "Editor di testo PDF" +desc = "Modifica testo e immagini esistenti nei PDF" [home.addText] tags = "testo,annotazione,etichetta" @@ -1400,7 +1408,7 @@ arabic = "Arabo" japanese = "Giapponese" korean = "Coreano" chinese = "Cinese" -thai = "Thai" +thai = "Tailandese" [watermark.steps] type = "Tipo di filigrana" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Firma disegnata" defaultImageLabel = "Firma caricata" defaultTextLabel = "Firma digitata" saveButton = "Salva firma" +savePersonal = "Salva come personale" +saveShared = "Salva come condivisa" saveUnavailable = "Crea prima una firma per salvarla." noChanges = "La firma corrente è già salvata." +tempStorageTitle = "Archiviazione temporanea del browser" +tempStorageDescription = "Le firme sono archiviate solo nel tuo browser. Verranno perse se cancelli i dati del browser o cambi browser." +personalHeading = "Firme personali" +sharedHeading = "Firme condivise" +personalDescription = "Solo tu puoi vedere queste firme." +sharedDescription = "Tutti gli utenti possono vedere e usare queste firme." [sign.saved.type] canvas = "Disegno" @@ -2841,8 +2857,8 @@ label = "Fattore di scala" [adjustPageScale.pageSize] label = "Dimensione pagina di destinazione" keep = "Mantieni dimensioni originali" -letter = "Letter" -legal = "Legal" +letter = "Lettera" +legal = "Legale" [adjustPageScale.error] failed = "Si è verificato un errore durante la regolazione della scala della pagina." @@ -3020,6 +3036,91 @@ title = "Ottieni informazioni in PDF" header = "Ottieni informazioni in PDF" submit = "Ottieni informazioni" downloadJson = "Scarica JSON" +processing = "Estrazione delle informazioni in corso..." +results = "Risultati" +noResults = "Esegui lo strumento per generare un report." +downloads = "Download" +noneDetected = "Nessuno rilevato" +indexTitle = "Indice" + +[getPdfInfo.report] +entryLabel = "Riepilogo completo delle informazioni" +shortTitle = "Informazioni PDF" + +[getPdfInfo.sections] +metadata = "Metadati" +formFields = "Campi del modulo" +basicInfo = "Informazioni di base" +documentInfo = "Informazioni sul documento" +compliance = "Conformità" +encryption = "Crittografia" +permissions = "Autorizzazioni" +other = "Altro" +perPageInfo = "Info per pagina" +tableOfContents = "Sommario" + +[getPdfInfo.other] +attachments = "Allegati" +embeddedFiles = "File incorporati" +javaScript = "JavaScript" +layers = "Livelli" +structureTree = "Albero della struttura" +xmp = "Metadati XMP" + +[getPdfInfo.perPage] +size = "Dimensioni" +annotations = "Annotazioni" +images = "Immagini" +links = "Link" +fonts = "Font" +xobjects = "Conteggi XObject" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Pagine" +fileSize = "Dimensione file" +pdfVersion = "Versione PDF" +language = "Lingua" +title = "Riepilogo PDF" +author = "Autore" +created = "Creato" +modified = "Modificato" +permsAll = "Tutte le autorizzazioni consentite" +permsRestricted = "{{count}} restrizioni" +permsMixed = "Alcune autorizzazioni limitate" +hasCompliance = "Conforme agli standard" +noCompliance = "Nessuno standard di conformità" +basic = "Informazioni di base" +documentInfo = "Informazioni sul documento" +securityTitle = "Stato della sicurezza" +technical = "Tecnico" +overviewTitle = "Panoramica PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF crittografato - Protezione tramite password presente" +unencrypted = "PDF non crittografato - Nessuna protezione tramite password" + +[getPdfInfo.summary.tech] +images = "Immagini" +fonts = "Font" +formFields = "Campi del modulo" +embeddedFiles = "File incorporati" +javaScript = "JavaScript" +layers = "Livelli" +bookmarks = "Segnalibri" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "un documento senza titolo" +unknown = "Autore sconosciuto" +text = "Questo è un PDF di {{pages}} pagine intitolato {{title}} creato da {{author}} (versione PDF {{version}})." + +[getPdfInfo.error] +partial = "Non è stato possibile elaborare alcuni file." +unexpected = "Errore imprevisto durante l'estrazione." + +[getPdfInfo.status] +complete = "Estrazione completata" [extractPage] tags = "estrarre" @@ -3438,6 +3539,9 @@ signinTitle = "Per favore accedi" ssoSignIn = "Accedi tramite Single Sign-on" oAuth2AutoCreateDisabled = "Creazione automatica utente OAUTH2 DISABILITATA" oAuth2AdminBlockedUser = "La registrazione o l'accesso degli utenti non registrati è attualmente bloccata. Si prega di contattare l'amministratore." +oAuth2RequiresLicense = "L'accesso OAuth/SSO richiede una licenza a pagamento (Server o Enterprise). Contatta l'amministratore per aggiornare il tuo piano." +saml2RequiresLicense = "L'accesso SAML richiede una licenza a pagamento (Server o Enterprise). Contatta l'amministratore per aggiornare il tuo piano." +maxUsersReached = "Numero massimo di utenti raggiunto per la licenza corrente. Contatta l'amministratore per aggiornare il piano o aggiungere altri posti." oauth2RequestNotFound = "Richiesta di autorizzazione non trovata" oauth2InvalidUserInfoResponse = "Risposta relativa alle informazioni utente non valida" oauth2invalidRequest = "Richiesta non valida" @@ -3846,14 +3950,17 @@ fitToWidth = "Adatta alla larghezza" actualSize = "Dimensione reale" [viewer] +cannotPreviewFile = "Impossibile visualizzare l'anteprima del file" +dualPageView = "Vista doppia pagina" firstPage = "Prima pagina" lastPage = "Ultima pagina" -previousPage = "Pagina precedente" nextPage = "Pagina successiva" +onlyPdfSupported = "Il visualizzatore supporta solo file PDF. Questo file sembra essere in un formato diverso." +previousPage = "Pagina precedente" +singlePageView = "Vista pagina singola" +unknownFile = "File sconosciuto" zoomIn = "Ingrandisci" zoomOut = "Riduci" -singlePageView = "Vista pagina singola" -dualPageView = "Vista doppia pagina" [rightRail] closeSelected = "Chiudi file selezionati" @@ -3877,6 +3984,7 @@ toggleSidebar = "Mostra/Nascondi barra laterale" exportSelected = "Esporta pagine selezionate" toggleAnnotations = "Attiva/disattiva visibilità annotazioni" annotationMode = "Attiva/disattiva modalità annotazione" +print = "Stampa PDF" draw = "Disegna" save = "Salva" saveChanges = "Salva modifiche" @@ -3925,7 +4033,7 @@ files = "File" activity = "Attività" help = "Guida" account = "Account" -config = "Config" +config = "Configurazione" settings = "Opzioni" adminSettings = "Opzioni Admin" allTools = "Funzioni" @@ -4153,7 +4261,7 @@ description = "Traccia azioni degli utenti ed eventi di sistema per conformità [admin.settings.security.audit.level] label = "Livello audit" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=SPENTO, 1=BASE, 2=STANDARD, 3=DETTAGLIATO" [admin.settings.security.audit.retentionDays] label = "Conservazione audit (giorni)" @@ -4487,13 +4595,14 @@ label = "Informativa sui cookie" description = "URL o nome file della cookie policy" [admin.settings.legal.impressum] -label = "Impressum" +label = "Note legali" description = "URL o nome file dell'Impressum (richiesto in alcune giurisdizioni)" [admin.settings.premium] title = "Premium e Enterprise" description = "Configura la tua chiave di licenza premium o enterprise." license = "Configurazione licenza" +noInput = "Fornisci una chiave o un file di licenza" [admin.settings.premium.licenseKey] toggle = "Hai una chiave di licenza o un file di certificato?" @@ -4511,6 +4620,25 @@ line1 = "La sovrascrittura della licenza attuale non può essere annullata." line2 = "La tua licenza precedente andrà persa in modo permanente a meno che tu non l'abbia salvata altrove." line3 = "Importante: mantieni le chiavi di licenza private e sicure. Non condividerle mai pubblicamente." +[admin.settings.premium.inputMethod] +text = "Chiave di licenza" +file = "File del certificato" + +[admin.settings.premium.file] +label = "File del certificato di licenza" +description = "Carica il file di licenza .lic o .cert degli acquisti offline" +choose = "Scegli file di licenza" +selected = "Selezionato: {{filename}} ({{size}})" +successMessage = "File di licenza caricato e attivato con successo. Non è richiesto il riavvio." + +[admin.settings.premium.currentLicense] +title = "Licenza attiva" +file = "Origine: File di licenza ({{path}})" +key = "Origine: Chiave di licenza" +type = "Tipo: {{type}}" +noInput = "Fornisci una chiave di licenza o carica un file di certificato" +success = "Successo" + [admin.settings.premium.enabled] label = "Abilita funzionalità Premium" description = "Abilita i controlli della chiave di licenza per funzionalità pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} selezionati" download = "Salva" delete = "Elimina" unsupported = "Non supportato" +active = "Attivo" addToUpload = "Aggiungi al caricamento" +closeFile = "Chiudi file" deleteAll = "Elimina tutto" loadingFiles = "Caricamento file..." noFiles = "Nessun file disponibile" @@ -5132,7 +5262,7 @@ upgrade = "Esegui upgrade ora →" freeTitle = "Licenza server" overLimitTitle = "Licenza server necessaria" overLimitBody = "La nostra licenza consente fino a {{freeTierLimit}} utenti gratuiti per server. Hai {{overLimitUserCopy}} utenti Stirling. Per continuare senza interruzioni, esegui l'upgrade al piano Stirling Server - posti illimitati, modifica del testo PDF e pieno controllo admin a $99/server/mese." -freeBody = "La nostra licenza Open-Core consente fino a {{freeTierLimit}} utenti gratuiti per server. Per scalare senza interruzioni e ottenere accesso anticipato al nuovo strumento di modifica testo PDF, consigliamo il piano Stirling Server - modifica completa e posti illimitati a $99/server/mese." +freeBody = "La nostra licenza Open-Core consente fino a {{freeTierLimit}} utenti gratuiti per server. Per scalare senza interruzioni, consigliamo il piano Stirling Server - posti illimitati e supporto SSO a $99/server/mese." [onboarding.desktopInstall] title = "Download" @@ -5194,7 +5324,7 @@ subtitle = "Digita o incolla le email qui sotto, separate da virgole. La tua are [workspace.people.actions] label = "Azioni" -upgrade = "Upgrade" +upgrade = "Aggiorna" [workspace.people.roleDescriptions] admin = "Può gestire impostazioni e invitare membri, con pieno accesso amministrativo." @@ -5237,6 +5367,31 @@ error = "Impossibile aggiornare lo stato utente" success = "Utente eliminato con successo" error = "Impossibile eliminare l'utente" +[workspace.people.changePassword] +action = "Cambia password" +title = "Cambia password" +subtitle = "Aggiorna la password per" +newPassword = "Nuova password" +confirmPassword = "Conferma password" +placeholder = "Inserisci una nuova password" +confirmPlaceholder = "Reinserisci la nuova password" +passwordRequired = "Inserisci una nuova password" +passwordMismatch = "Le password non coincidono" +generateRandom = "Genera password sicura" +generatedPreview = "Password generata:" +copyTooltip = "Copia negli appunti" +copiedToClipboard = "Password copiata negli appunti" +copyFailed = "Impossibile copiare la password" +sendEmail = "Invia un'email all'utente riguardo a questa modifica" +includePassword = "Includi la nuova password nell'email" +forcePasswordChange = "Obbliga l'utente a cambiare password al prossimo accesso" +emailUnavailable = "L'email di questo utente non è un indirizzo valido. Le notifiche sono disabilitate." +smtpDisabled = "Le notifiche email richiedono che SMTP sia abilitato nelle impostazioni." +notifyOnly = "Verrà inviata un'email senza la password, informando l'utente che un amministratore l'ha modificata." +submit = "Aggiorna password" +success = "Password aggiornata correttamente" +error = "Impossibile aggiornare la password" + [workspace.people.emailInvite] tab = "Invito via email" description = "Digita o incolla le email qui sotto, separate da virgole. Gli utenti riceveranno le credenziali di accesso via email." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "È richiesto almeno un indirizzo email" submit = "Invia inviti" success = "utente/i invitato/i con successo" -partialSuccess = "Alcuni inviti non sono riusciti" +partialFailure = "Alcuni inviti non sono riusciti" allFailed = "Impossibile invitare gli utenti" error = "Invio inviti non riuscito" @@ -5752,7 +5907,7 @@ label = "Seleziona server" description = "Server self-hosted" [setup.step3] -label = "Login" +label = "Accesso" description = "Inserisci credenziali" [setup.mode.saas] @@ -5770,6 +5925,7 @@ subtitle = "Accedi con il tuo account Stirling" [setup.selfhosted] title = "Accedi al server" subtitle = "Inserisci le credenziali del server" +link = "oppure connettiti a un account self-hosted" [setup.server] title = "Connetti al server" @@ -5788,15 +5944,30 @@ description = "Inserisci l'URL completo del tuo server Stirling PDF self-hosted" emptyUrl = "Inserisci un URL del server" unreachable = "Impossibile connettersi al server" testFailed = "Test di connessione non riuscito" +configFetch = "Impossibile recuperare la configurazione del server. Controlla l'URL e riprova." + +[setup.server.error.securityDisabled] +title = "Accesso non abilitato" +body = "L'accesso non è abilitato su questo server. Per connetterti, devi abilitare l'autenticazione:" +step1 = "Imposta DOCKER_ENABLE_SECURITY=true nel tuo ambiente" +step2 = "Oppure imposta security.enableLogin=true in settings.yml" +step3 = "Riavvia il server" [setup.login] title = "Accedi" subtitle = "Inserisci le credenziali per continuare" connectingTo = "Connessione a:" -submit = "Login" +submit = "Accedi" signInWith = "Accedi con" oauthPending = "Apertura del browser per l'autenticazione..." orContinueWith = "Oppure continua con email" +serverRequirement = "Nota: il server deve avere il login abilitato." +showInstructions = "Come abilitarlo?" +hideInstructions = "Nascondi istruzioni" +instructions = "Per abilitare il login sul tuo server Stirling PDF:" +instructionsEnvVar = "Imposta la variabile d'ambiente:" +instructionsOrYml = "Oppure in settings.yml:" +instructionsRestart = "Quindi riavvia il server affinché le modifiche abbiano effetto." [setup.login.username] label = "Nome utente" @@ -5840,7 +6011,7 @@ paragraph = "Pagina a paragrafi" sparse = "Testo sparso" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automatico" paragraph = "Paragrafo" singleLine = "Riga singola" @@ -5853,6 +6024,7 @@ earlyAccess = "Accesso anticipato" reset = "Reimposta modifiche" downloadJson = "Scarica JSON" generatePdf = "Genera PDF" +saveChanges = "Salva modifiche" [pdfTextEditor.options.autoScaleText] title = "Ridimensiona automaticamente il testo alle caselle" @@ -5890,6 +6062,8 @@ alpha = "Questo visualizzatore alpha è in evoluzione: alcuni font, colori, effe [pdfTextEditor.empty] title = "Nessun documento caricato" subtitle = "Carica un file PDF o JSON per iniziare a modificare il testo." +dropzone = "Trascina qui un file PDF o JSON oppure fai clic per sfogliare" +dropzoneWithFiles = "Seleziona un file dalla scheda File oppure trascina qui un file PDF o JSON, o fai clic per sfogliare" [pdfTextEditor.welcomeBanner] title = "Benvenuto in PDF Text Editor (Accesso anticipato)" diff --git a/frontend/public/locales/ja-JP/translation.toml b/frontend/public/locales/ja-JP/translation.toml index 90b5eeee4..dda2f9b81 100644 --- a/frontend/public/locales/ja-JP/translation.toml +++ b/frontend/public/locales/ja-JP/translation.toml @@ -163,6 +163,11 @@ unfavorite = "お気に入りから削除" fullscreen = "フルスクリーンモードに切り替え" sidebar = "サイドバーモードに切り替え" +[backendStartup] +notFoundTitle = "バックエンドが見つかりません" +retry = "再試行" +unreachable = "現在、アプリケーションはバックエンドに接続できません。バックエンドの稼働状況とネットワーク接続を確認し、再度お試しください。" + [zipWarning] title = "大きな ZIP ファイル" message = "このZIPには{{count}}個のファイルが含まれています。展開しますか?" @@ -912,6 +917,9 @@ desc = "PDF アクションを連結して複数ステップのワークフロ desc = "1つのPDFを別のPDFの上に重ねます" title = "PDFを重ね合わせ" +[home.pdfTextEditor] +title = "PDFテキストエディタ" +desc = "PDF内の既存のテキストと画像を編集" [home.addText] tags = "テキスト,注釈,ラベル" @@ -1173,7 +1181,7 @@ selectFilesPlaceholder = "開始するにはメインビューでファイルを settings = "設定" conversionCompleted = "変換が完了しました" results = "結果" -defaultFilename = "converted_file" +defaultFilename = "変換済みファイル" conversionResults = "変換結果" convertFrom = "変換元" convertTo = "変換先" @@ -1360,7 +1368,7 @@ title = "透かしの追加" desc = "PDF ファイルにテキストまたは画像の透かしを追加" completed = "透かしを追加しました" submit = "透かしを追加" -filenamePrefix = "watermarked" +filenamePrefix = "透かし入り" [watermark.error] failed = "PDF への透かし追加中にエラーが発生しました。" @@ -1635,7 +1643,7 @@ subtitle = "処理済みファイルをダウンロードするか、下で操 [removePages] tags = "ページを削除,ページ削除" title = "削除" -filenamePrefix = "pages_removed" +filenamePrefix = "ページ削除済み" submit = "削除" [removePages.pageNumbers] @@ -1837,7 +1845,7 @@ title = "フォームフィールドから読み取り専用を削除" header = "PDFフォームのロックを解除" submit = "Remove" description = "このツールは PDF フォームフィールドの読み取り専用制限を解除し、編集・入力可能にします。" -filenamePrefix = "unlocked_forms" +filenamePrefix = "フォームのロック解除済み" [unlockPDFForms.files] placeholder = "メインビューで PDF ファイルを選択して開始してください" @@ -1851,7 +1859,7 @@ title = "フォームのロック解除結果" [changeMetadata] header = "メタデータの変更" submit = "変更" -filenamePrefix = "metadata" +filenamePrefix = "メタデータ" [changeMetadata.settings] title = "メタデータ設定" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "手書き署名" defaultImageLabel = "アップロードした署名" defaultTextLabel = "入力した署名" saveButton = "署名を保存" +savePersonal = "個人用として保存" +saveShared = "共有用として保存" saveUnavailable = "まず署名を作成してから保存してください。" noChanges = "現在の署名はすでに保存済みです。" +tempStorageTitle = "ブラウザーの一時ストレージ" +tempStorageDescription = "署名はブラウザー内のみに保存されます。ブラウザーのデータを消去するか、別のブラウザーに切り替えると失われます。" +personalHeading = "個人用署名" +sharedHeading = "共有署名" +personalDescription = "これらの署名はあなただけが表示できます。" +sharedDescription = "すべてのユーザーがこれらの署名を表示して使用できます。" [sign.saved.type] canvas = "描画" @@ -2318,7 +2334,7 @@ title = "平坦化" header = "PDFを平坦化する" flattenOnlyForms = "フォームのみを平坦にする" submit = "平坦化" -filenamePrefix = "flattened" +filenamePrefix = "フラット化済み" [flatten.files] placeholder = "開始するにはメインビューで PDF ファイルを選択してください" @@ -2366,7 +2382,7 @@ title = "修復" header = "PDFを修復" submit = "修復" description = "このツールは破損または損傷した PDF ファイルの修復を試みます。追加の設定は不要です。" -filenamePrefix = "repaired" +filenamePrefix = "修復済み" [repair.files] placeholder = "開始するにはメイン画面で PDF ファイルを選択してください" @@ -2567,7 +2583,7 @@ stopButton = "比較を停止" [certSign] tags = "authenticate,PEM,P12,official,encrypt" title = "証明書による署名" -filenamePrefix = "signed" +filenamePrefix = "署名済み" chooseCertificate = "証明書ファイルを選択" chooseJksFile = "JKS ファイルを選択" chooseP12File = "PKCS12 ファイルを選択" @@ -2701,7 +2717,7 @@ header = "PDFから電子証明書を削除する" selectPDF = "PDFファイルの選択:" submit = "署名の削除" description = "このツールは PDF 文書からデジタル証明書署名を削除します。" -filenamePrefix = "unsigned" +filenamePrefix = "署名なし" [removeCertSign.files] placeholder = "開始するにはメイン画面で PDF ファイルを選択してください" @@ -3020,6 +3036,91 @@ title = "PDFの情報を入手" header = "PDFの情報を入手" submit = "情報を入手" downloadJson = "JSONでダウンロード" +processing = "情報を抽出しています..." +results = "結果" +noResults = "レポートを生成するにはツールを実行してください。" +downloads = "ダウンロード" +noneDetected = "検出なし" +indexTitle = "インデックス" + +[getPdfInfo.report] +entryLabel = "全情報の概要" +shortTitle = "PDF情報" + +[getPdfInfo.sections] +metadata = "メタデータ" +formFields = "フォームフィールド" +basicInfo = "基本情報" +documentInfo = "ドキュメント情報" +compliance = "準拠" +encryption = "暗号化" +permissions = "権限" +other = "その他" +perPageInfo = "ページごとの情報" +tableOfContents = "目次" + +[getPdfInfo.other] +attachments = "添付ファイル" +embeddedFiles = "埋め込みファイル" +javaScript = "JavaScript" +layers = "レイヤー" +structureTree = "構造ツリー" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "サイズ" +annotations = "注釈" +images = "画像" +links = "リンク" +fonts = "フォント" +xobjects = "XObject の数" +multimedia = "マルチメディア" + +[getPdfInfo.summary] +pages = "ページ数" +fileSize = "ファイルサイズ" +pdfVersion = "PDF バージョン" +language = "言語" +title = "PDF 概要" +author = "作成者" +created = "作成日" +modified = "更新日" +permsAll = "すべての権限が許可されています" +permsRestricted = "{{count}} 件の制限" +permsMixed = "一部の権限が制限されています" +hasCompliance = "準拠規格あり" +noCompliance = "準拠規格なし" +basic = "基本情報" +documentInfo = "ドキュメント情報" +securityTitle = "セキュリティ状態" +technical = "技術情報" +overviewTitle = "PDF 概要" + +[getPdfInfo.summary.security] +encrypted = "暗号化された PDF - パスワード保護あり" +unencrypted = "暗号化されていない PDF - パスワード保護なし" + +[getPdfInfo.summary.tech] +images = "画像" +fonts = "フォント" +formFields = "フォームフィールド" +embeddedFiles = "埋め込みファイル" +javaScript = "JavaScript" +layers = "レイヤー" +bookmarks = "ブックマーク" +multimedia = "マルチメディア" + +[getPdfInfo.summary.overview] +untitled = "無題のドキュメント" +unknown = "不明な作成者" +text = "これは {{author}} によって作成された、タイトル {{title}} の {{pages}} ページの PDF です(PDF バージョン {{version}})。" + +[getPdfInfo.error] +partial = "一部のファイルを処理できませんでした。" +unexpected = "抽出中に予期しないエラーが発生しました。" + +[getPdfInfo.status] +complete = "抽出が完了しました" [extractPage] tags = "抽出" @@ -3438,6 +3539,9 @@ signinTitle = "サインインしてください" ssoSignIn = "シングルサインオンでログイン" oAuth2AutoCreateDisabled = "OAuth 2自動作成ユーザーが無効" oAuth2AdminBlockedUser = "現在、未登録ユーザーの登録またはログインはブロックされています。管理者にお問い合わせください。" +oAuth2RequiresLicense = "OAuth/SSO ログインには有料ライセンス(Server または Enterprise)が必要です。プランのアップグレードについては管理者にお問い合わせください。" +saml2RequiresLicense = "SAML ログインには有料ライセンス(Server または Enterprise)が必要です。プランのアップグレードについては管理者にお問い合わせください。" +maxUsersReached = "現在のライセンスのユーザー数上限に達しました。プランのアップグレードまたはシート数の追加について、管理者にお問い合わせください。" oauth2RequestNotFound = "認証リクエストが見つかりません" oauth2InvalidUserInfoResponse = "無効なユーザー情報の応答" oauth2invalidRequest = "無効なリクエスト" @@ -3533,7 +3637,7 @@ title = "PDFを単一ページに変換" header = "PDFを単一ページに変換" submit = "単一ページに変換" description = "このツールは PDF の全ページを 1 つの大きな単一ページに結合します。幅は元のページと同じで、高さは全ページの高さの合計になります。" -filenamePrefix = "single_page" +filenamePrefix = "単一ページ" [pdfToSinglePage.files] placeholder = "開始するにはメイン画面で PDF ファイルを選択してください" @@ -3846,14 +3950,17 @@ fitToWidth = "幅に合わせる" actualSize = "原寸" [viewer] +cannotPreviewFile = "ファイルをプレビューできません" +dualPageView = "見開き表示" firstPage = "最初のページ" lastPage = "最後のページ" -previousPage = "前のページ" nextPage = "次のページ" +onlyPdfSupported = "このビューアは PDF ファイルのみをサポートしています。このファイルは別の形式のようです。" +previousPage = "前のページ" +singlePageView = "単一ページ表示" +unknownFile = "不明なファイル" zoomIn = "拡大" zoomOut = "縮小" -singlePageView = "単一ページ表示" -dualPageView = "見開き表示" [rightRail] closeSelected = "選択したファイルを閉じる" @@ -3877,6 +3984,7 @@ toggleSidebar = "サイドバーを切り替え" exportSelected = "選択したページを書き出し" toggleAnnotations = "注釈の表示を切り替え" annotationMode = "注釈モードを切り替え" +print = "PDFを印刷" draw = "描画" save = "保存" saveChanges = "変更を保存" @@ -4231,15 +4339,15 @@ label = "プロバイダ" description = "認証に使用する OAuth2 プロバイダ" [admin.settings.connections.oauth2.issuer] -label = "Issuer URL" +label = "発行者 URL" description = "OAuth2 プロバイダの Issuer URL" [admin.settings.connections.oauth2.clientId] -label = "Client ID" +label = "クライアント ID" description = "プロバイダから発行された OAuth2 の Client ID" [admin.settings.connections.oauth2.clientSecret] -label = "Client Secret" +label = "クライアント シークレット" description = "プロバイダから発行された OAuth2 の Client Secret" [admin.settings.connections.oauth2.useAsUsername] @@ -4270,7 +4378,7 @@ label = "プロバイダ" description = "SAML2 プロバイダ名" [admin.settings.connections.saml2.registrationId] -label = "Registration ID" +label = "登録 ID" description = "SAML2 の登録識別子" [admin.settings.connections.saml2.autoCreateUser] @@ -4407,7 +4515,7 @@ description = "より広範なシステム一時ディレクトリをクリー label = "プロセス実行制限" description = "各プロセス実行器のセッション上限とタイムアウトを設定" libreOffice = "LibreOffice" -pdfToHtml = "PDF to HTML" +pdfToHtml = "PDF を HTML に" qpdf = "QPDF" tesseract = "Tesseract OCR" pythonOpenCv = "Python OpenCV" @@ -4494,6 +4602,7 @@ description = "インプリントへの URL またはファイル名(地域に title = "プレミアムとエンタープライズ" description = "プレミアムまたはエンタープライズのライセンスキーを構成します。" license = "ライセンス設定" +noInput = "ライセンスキーまたはファイルを入力してください" [admin.settings.premium.licenseKey] toggle = "ライセンスキーまたは証明書ファイルをお持ちですか?" @@ -4511,6 +4620,25 @@ line1 = "現在のライセンスキーを上書きすると元に戻せませ line2 = "別途バックアップしていない限り、以前のライセンスは永久に失われます。" line3 = "重要: ライセンスキーは秘密に安全に保管してください。公開で共有しないでください。" +[admin.settings.premium.inputMethod] +text = "ライセンスキー" +file = "証明書ファイル" + +[admin.settings.premium.file] +label = "ライセンス証明書ファイル" +description = "オフライン購入の .lic または .cert ライセンスファイルをアップロードしてください" +choose = "ライセンスファイルを選択" +selected = "選択済み: {{filename}} ({{size}})" +successMessage = "ライセンスファイルをアップロードして有効化しました。再起動は不要です。" + +[admin.settings.premium.currentLicense] +title = "有効なライセンス" +file = "ソース: ライセンスファイル ({{path}})" +key = "ソース: ライセンスキー" +type = "種類: {{type}}" +noInput = "ライセンスキーを入力するか、証明書ファイルをアップロードしてください" +success = "成功" + [admin.settings.premium.enabled] label = "プレミアム機能を有効化" description = "Pro/Enterprise 機能のライセンスキー検証を有効化" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} 件選択" download = "ダウンロード" delete = "削除" unsupported = "未対応" +active = "アクティブ" addToUpload = "アップロードに追加" +closeFile = "ファイルを閉じる" deleteAll = "すべて削除" loadingFiles = "ファイルを読み込み中..." noFiles = "ファイルはありません" @@ -4669,7 +4799,7 @@ title = "サニタイズ" desc = "PDF ファイルから潜在的に有害な要素を削除します。" submit = "PDFをサニタイズ" completed = "サニタイズが正常に完了しました" -filenamePrefix = "sanitised" +filenamePrefix = "サニタイズ済み" sanitizationResults = "サニタイズ結果" [sanitize.error] @@ -4717,7 +4847,7 @@ title = "パスワードの追加" desc = "パスワードで PDF 文書を暗号化します。" completed = "パスワード保護を適用しました" submit = "暗号化" -filenamePrefix = "encrypted" +filenamePrefix = "暗号化済み" [addPassword.error] failed = "PDF の暗号化中にエラーが発生しました。" @@ -4812,7 +4942,7 @@ text = "これらの権限を変更不可にするには、パスワード追加 title = "パスワードの削除" desc = "PDFからパスワードの削除します。" tags = "セキュア,復号,セキュリティ,パスワード解除,パスワード削除" -filenamePrefix = "decrypted" +filenamePrefix = "復号済み" submit = "削除" [removePassword.password] @@ -5132,7 +5262,7 @@ upgrade = "今すぐアップグレード →" freeTitle = "サーバーライセンス" overLimitTitle = "サーバーライセンスが必要です" overLimitBody = "当社のライセンスでは、サーバーごとに {{freeTierLimit}} ユーザーまで無料です。現在 {{overLimitUserCopy}} の Stirling ユーザーがいます。中断なく利用を続けるには、Stirling Server プランにアップグレードしてください - 無制限席数、PDF テキスト編集、完全な管理機能が $99/サーバー/月 です。" -freeBody = "当社の オープンコア ライセンスでは、サーバーごとに最大 {{freeTierLimit}} ユーザーまで無料です。中断なく拡張し、新しい PDF テキスト編集ツール に早期アクセスするには、Stirling Server プランをお勧めします。完全編集と 無制限席数 が $99/サーバー/月 です。" +freeBody = "当社のOpen-Coreライセンスでは、サーバーごとに最大{{freeTierLimit}}ユーザーまで無料でご利用いただけます。中断なくスケールするには、Stirling Server プランをおすすめします - 無制限の席数SSO サポートで $99/サーバー/月。" [onboarding.desktopInstall] title = "ダウンロード" @@ -5237,6 +5367,31 @@ error = "ユーザーのステータスの更新に失敗しました" success = "ユーザーを削除しました" error = "ユーザーの削除に失敗しました" +[workspace.people.changePassword] +action = "パスワードを変更" +title = "パスワードを変更" +subtitle = "のパスワードを更新" +newPassword = "新しいパスワード" +confirmPassword = "パスワードの確認" +placeholder = "新しいパスワードを入力" +confirmPlaceholder = "新しいパスワードを再入力" +passwordRequired = "新しいパスワードを入力してください" +passwordMismatch = "パスワードが一致しません" +generateRandom = "安全なパスワードを生成" +generatedPreview = "生成されたパスワード:" +copyTooltip = "クリップボードにコピー" +copiedToClipboard = "パスワードをクリップボードにコピーしました" +copyFailed = "パスワードのコピーに失敗しました" +sendEmail = "この変更についてユーザーにメールする" +includePassword = "メールに新しいパスワードを含める" +forcePasswordChange = "次回ログイン時にユーザーにパスワード変更を強制する" +emailUnavailable = "このユーザーのメールは有効なメールアドレスではありません。通知は無効です。" +smtpDisabled = "メール通知には設定で SMTP を有効にする必要があります。" +notifyOnly = "パスワードを含めずにメールを送信し、管理者が変更したことをユーザーに知らせます。" +submit = "パスワードを更新" +success = "パスワードを更新しました" +error = "パスワードの更新に失敗しました" + [workspace.people.emailInvite] tab = "メール招待" description = "下にメールアドレスをカンマ区切りで入力または貼り付けてください。ユーザーにはメールでログイン情報が届きます。" @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "少なくとも1件のメールアドレスが必要です" submit = "招待を送信" success = "ユーザーを招待しました" -partialSuccess = "一部の招待に失敗しました" +partialFailure = "一部の招待に失敗しました" allFailed = "ユーザーの招待に失敗しました" error = "招待の送信に失敗しました" @@ -5770,6 +5925,7 @@ subtitle = "Stirling アカウントでサインイン" [setup.selfhosted] title = "サーバーにサインイン" subtitle = "サーバーの認証情報を入力" +link = "またはセルフホスト型アカウントに接続" [setup.server] title = "サーバーに接続" @@ -5788,6 +5944,14 @@ description = "セルフホストの Stirling PDF サーバーの完全な URL emptyUrl = "サーバー URL を入力してください" unreachable = "サーバーに接続できませんでした" testFailed = "接続テストに失敗しました" +configFetch = "サーバー構成の取得に失敗しました。URL を確認して、もう一度お試しください。" + +[setup.server.error.securityDisabled] +title = "ログインが有効になっていません" +body = "このサーバーではログインが有効になっていません。このサーバーに接続するには、認証を有効にする必要があります:" +step1 = "環境で DOCKER_ENABLE_SECURITY=true を設定する" +step2 = "または settings.yml で security.enableLogin=true を設定する" +step3 = "サーバーを再起動する" [setup.login] title = "サインイン" @@ -5797,13 +5961,20 @@ submit = "ログイン" signInWith = "でサインイン" oauthPending = "認証のためブラウザーを開いています..." orContinueWith = "またはメールで続行" +serverRequirement = "注: サーバーでログインを有効にする必要があります。" +showInstructions = "有効化するには?" +hideInstructions = "手順を非表示" +instructions = "Stirling PDF サーバーでログインを有効にするには:" +instructionsEnvVar = "環境変数を設定:" +instructionsOrYml = "または settings.yml で:" +instructionsRestart = "その後、サーバーを再起動して変更を反映させてください。" [setup.login.username] label = "ユーザー名" placeholder = "ユーザー名を入力" [setup.login.email] -label = "Email" +label = "メールアドレス" placeholder = "メールアドレスを入力" [setup.login.password] @@ -5853,6 +6024,7 @@ earlyAccess = "早期アクセス" reset = "変更をリセット" downloadJson = "JSON をダウンロード" generatePdf = "PDF を生成" +saveChanges = "変更を保存" [pdfTextEditor.options.autoScaleText] title = "ボックスに収まるようテキストを自動スケール" @@ -5890,6 +6062,8 @@ alpha = "このアルファ版ビューアは開発途上です。一部のフ [pdfTextEditor.empty] title = "ドキュメントが読み込まれていません" subtitle = "テキスト編集を開始するにはPDFまたはJSONファイルを読み込んでください。" +dropzone = "ここに PDF または JSON ファイルをドラッグ&ドロップするか、クリックして参照" +dropzoneWithFiles = "ファイルタブからファイルを選択するか、ここに PDF または JSON ファイルをドラッグ&ドロップするか、クリックして参照" [pdfTextEditor.welcomeBanner] title = "PDF Text Editor(早期アクセス)へようこそ" diff --git a/frontend/public/locales/ko-KR/translation.toml b/frontend/public/locales/ko-KR/translation.toml index f63fd82c6..63c1018c9 100644 --- a/frontend/public/locales/ko-KR/translation.toml +++ b/frontend/public/locales/ko-KR/translation.toml @@ -163,6 +163,11 @@ unfavorite = "즐겨찾기에서 제거" fullscreen = "전체 화면 모드로 전환" sidebar = "사이드바 모드로 전환" +[backendStartup] +notFoundTitle = "백엔드를 찾을 수 없음" +retry = "재시도" +unreachable = "현재 애플리케이션이 백엔드에 연결할 수 없습니다. 백엔드 상태와 네트워크 연결을 확인한 후 다시 시도하세요." + [zipWarning] title = "큰 ZIP 파일" message = "이 ZIP에는 {{count}}개의 파일이 포함되어 있습니다. 그래도 압축을 해제하시겠습니까?" @@ -912,6 +917,9 @@ desc = "PDF 작업을 연결하여 다단계 워크플로를 구성하세요. desc = "PDF를 다른 PDF 위에 오버레이" title = "PDF 오버레이" +[home.pdfTextEditor] +title = "PDF 텍스트 편집기" +desc = "PDF 내부의 기존 텍스트와 이미지를 편집합니다" [home.addText] tags = "텍스트,주석,레이블" @@ -1901,8 +1909,8 @@ placeholder = "수정 날짜" [changeMetadata.trapped] label = "트래핑 상태" unknown = "알 수 없음" -true = "True" -false = "False" +true = "참" +false = "거짓" [changeMetadata.advanced] title = "고급 옵션" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "그린 서명" defaultImageLabel = "업로드된 서명" defaultTextLabel = "입력한 서명" saveButton = "서명 저장" +savePersonal = "개인용으로 저장" +saveShared = "공유용으로 저장" saveUnavailable = "먼저 서명을 만든 후 저장하세요." noChanges = "현재 서명이 이미 저장되어 있습니다." +tempStorageTitle = "임시 브라우저 저장소" +tempStorageDescription = "서명은 브라우저에만 저장됩니다. 브라우저 데이터를 삭제하거나 브라우저를 변경하면 사라집니다." +personalHeading = "개인 서명" +sharedHeading = "공유 서명" +personalDescription = "이 서명은 본인만 볼 수 있습니다." +sharedDescription = "모든 사용자가 이 서명을 보고 사용할 수 있습니다." [sign.saved.type] canvas = "그리기" @@ -3020,6 +3036,91 @@ title = "PDF 정보 가져오기" header = "PDF 정보 가져오기" submit = "정보 가져오기" downloadJson = "JSON 다운로드" +processing = "정보를 추출하는 중..." +results = "결과" +noResults = "보고서를 생성하려면 도구를 실행하세요." +downloads = "다운로드" +noneDetected = "감지되지 않음" +indexTitle = "색인" + +[getPdfInfo.report] +entryLabel = "전체 정보 요약" +shortTitle = "PDF 정보" + +[getPdfInfo.sections] +metadata = "메타데이터" +formFields = "양식 필드" +basicInfo = "기본 정보" +documentInfo = "문서 정보" +compliance = "규격 준수" +encryption = "암호화" +permissions = "권한" +other = "기타" +perPageInfo = "페이지별 정보" +tableOfContents = "목차" + +[getPdfInfo.other] +attachments = "첨부 파일" +embeddedFiles = "내장 파일" +javaScript = "JavaScript" +layers = "레이어" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "크기" +annotations = "주석" +images = "이미지" +links = "링크" +fonts = "글꼴" +xobjects = "XObject 개수" +multimedia = "멀티미디어" + +[getPdfInfo.summary] +pages = "페이지 수" +fileSize = "파일 크기" +pdfVersion = "PDF 버전" +language = "언어" +title = "PDF 요약" +author = "작성자" +created = "생성됨" +modified = "수정됨" +permsAll = "모든 권한 허용" +permsRestricted = "{{count}}개의 제한" +permsMixed = "일부 권한이 제한됨" +hasCompliance = "규격 준수 기준 있음" +noCompliance = "규격 준수 기준 없음" +basic = "기본 정보" +documentInfo = "문서 정보" +securityTitle = "보안 상태" +technical = "기술 정보" +overviewTitle = "PDF 개요" + +[getPdfInfo.summary.security] +encrypted = "암호화된 PDF - 암호 보호 적용" +unencrypted = "암호화되지 않은 PDF - 암호 보호 없음" + +[getPdfInfo.summary.tech] +images = "이미지" +fonts = "글꼴" +formFields = "양식 필드" +embeddedFiles = "내장 파일" +javaScript = "JavaScript" +layers = "레이어" +bookmarks = "북마크" +multimedia = "멀티미디어" + +[getPdfInfo.summary.overview] +untitled = "제목 없는 문서" +unknown = "알 수 없는 작성자" +text = "이 문서는 {{pages}}페이지 분량의 PDF로, 제목은 {{title}}이며 작성자는 {{author}}입니다(PDF 버전 {{version}})." + +[getPdfInfo.error] +partial = "일부 파일을 처리하지 못했습니다." +unexpected = "추출 중 예기치 않은 오류가 발생했습니다." + +[getPdfInfo.status] +complete = "추출 완료" [extractPage] tags = "추출" @@ -3438,6 +3539,9 @@ signinTitle = "로그인해 주세요" ssoSignIn = "단일 로그인으로 로그인" oAuth2AutoCreateDisabled = "OAuth2 사용자 자동 생성이 비활성화되었습니다" oAuth2AdminBlockedUser = "현재 미등록 사용자의 등록 또는 로그인이 차단되어 있습니다. 관리자에게 문의하세요." +oAuth2RequiresLicense = "OAuth/SSO 로그인은 유료 라이선스(서버 또는 엔터프라이즈)가 필요합니다. 플랜 업그레이드를 위해 관리자에게 문의하세요." +saml2RequiresLicense = "SAML 로그인은 유료 라이선스(서버 또는 엔터프라이즈)가 필요합니다. 플랜 업그레이드를 위해 관리자에게 문의하세요." +maxUsersReached = "현재 라이선스에서 허용된 최대 사용자 수에 도달했습니다. 플랜 업그레이드 또는 시트 추가를 위해 관리자에게 문의하세요." oauth2RequestNotFound = "인증 요청을 찾을 수 없습니다" oauth2InvalidUserInfoResponse = "잘못된 사용자 정보 응답" oauth2invalidRequest = "잘못된 요청" @@ -3846,14 +3950,17 @@ fitToWidth = "너비에 맞추기" actualSize = "실제 크기" [viewer] +cannotPreviewFile = "파일을 미리보기할 수 없습니다" +dualPageView = "두 페이지 보기" firstPage = "첫 페이지" lastPage = "마지막 페이지" -previousPage = "이전 페이지" nextPage = "다음 페이지" +onlyPdfSupported = "뷰어는 PDF 파일만 지원합니다. 이 파일은 다른 형식인 것으로 보입니다." +previousPage = "이전 페이지" +singlePageView = "단일 페이지 보기" +unknownFile = "알 수 없는 파일" zoomIn = "확대" zoomOut = "축소" -singlePageView = "단일 페이지 보기" -dualPageView = "두 페이지 보기" [rightRail] closeSelected = "선택한 파일 닫기" @@ -3877,6 +3984,7 @@ toggleSidebar = "사이드바 전환" exportSelected = "선택한 페이지 내보내기" toggleAnnotations = "주석 가시성 전환" annotationMode = "주석 모드 전환" +print = "PDF 인쇄" draw = "그리기" save = "저장" saveChanges = "변경 내용 저장" @@ -4153,7 +4261,7 @@ description = "컴플라이언스 및 보안 모니터링을 위해 사용자 [admin.settings.security.audit.level] label = "감사 수준" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=끄기, 1=기본, 2=표준, 3=상세" [admin.settings.security.audit.retentionDays] label = "감사 로그 보존 기간(일)" @@ -4407,7 +4515,7 @@ description = "더 넓은 시스템 임시 디렉터리를 정리할지 여부( label = "프로세스 실행기 제한" description = "각 프로세스 실행기의 세션 제한 및 시간 제한을 구성합니다" libreOffice = "LibreOffice" -pdfToHtml = "PDF to HTML" +pdfToHtml = "PDF를 HTML로" qpdf = "QPDF" tesseract = "Tesseract OCR" pythonOpenCv = "Python OpenCV" @@ -4487,13 +4595,14 @@ label = "쿠키 정책" description = "쿠키 정책의 URL 또는 파일 이름" [admin.settings.legal.impressum] -label = "Impressum" +label = "법적 고지" description = "Impressum의 URL 또는 파일 이름(일부 관할권에서 필수)" [admin.settings.premium] title = "프리미엄 및 엔터프라이즈" description = "프리미엄 또는 엔터프라이즈 라이선스 키를 구성합니다." license = "라이선스 구성" +noInput = "라이선스 키 또는 파일을 입력해 주세요" [admin.settings.premium.licenseKey] toggle = "라이선스 키나 인증서 파일이 있나요?" @@ -4511,6 +4620,25 @@ line1 = "현재 라이선스 키를 덮어쓰면 되돌릴 수 없습니다." line2 = "다른 곳에 백업하지 않았다면 이전 라이선스는 영구적으로 손실됩니다." line3 = "중요: 라이선스 키는 개인적으로 안전하게 보관하세요. 공개적으로 공유하지 마세요." +[admin.settings.premium.inputMethod] +text = "라이선스 키" +file = "인증서 파일" + +[admin.settings.premium.file] +label = "라이선스 인증서 파일" +description = "오프라인 구매 시 받은 .lic 또는 .cert 라이선스 파일을 업로드하세요" +choose = "라이선스 파일 선택" +selected = "선택됨: {{filename}} ({{size}})" +successMessage = "라이선스 파일이 업로드되어 성공적으로 활성화되었습니다. 재시작은 필요하지 않습니다." + +[admin.settings.premium.currentLicense] +title = "활성 라이선스" +file = "소스: 라이선스 파일 ({{path}})" +key = "소스: 라이선스 키" +type = "유형: {{type}}" +noInput = "라이선스 키를 입력하거나 인증서 파일을 업로드해 주세요" +success = "성공" + [admin.settings.premium.enabled] label = "프리미엄 기능 활성화" description = "프로/엔터프라이즈 기능에 대한 라이선스 키 확인 활성화" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}}개 선택됨" download = "다운로드" delete = "삭제" unsupported = "지원되지 않음" +active = "활성" addToUpload = "업로드에 추가" +closeFile = "파일 닫기" deleteAll = "모두 삭제" loadingFiles = "파일 불러오는 중..." noFiles = "사용 가능한 파일이 없습니다" @@ -5132,7 +5262,7 @@ upgrade = "지금 업그레이드 →" freeTitle = "서버 라이선스" overLimitTitle = "서버 라이선스 필요" overLimitBody = "당사의 라이선스는 서버당 무료로 최대 {{freeTierLimit}}명의 사용자를 허용합니다. 현재 {{overLimitUserCopy}}명의 Stirling 사용자가 있습니다. 중단 없이 계속 사용하려면 Stirling Server 플랜으로 업그레이드하세요 - 무제한 좌석, PDF 텍스트 편집, 전체 관리자 제어 제공, $99/서버/월." -freeBody = "당사의 Open-Core 라이선스는 서버당 최대 {{freeTierLimit}}명의 사용자를 무료로 허용합니다. 중단 없이 확장하고 새로운 PDF 텍스트 편집 도구에 조기 액세스하려면 Stirling Server 플랜을 권장합니다 - 전체 편집과 무제한 좌석을 $99/서버/월에 제공합니다." +freeBody = "당사의 Open-Core 라이선스는 서버당 최대 {{freeTierLimit}}명의 사용자를 무료로 허용합니다. 중단 없이 확장하려면 Stirling Server 플랜을 권장합니다 - 무제한 좌석SSO 지원, $99/서버/월." [onboarding.desktopInstall] title = "다운로드" @@ -5237,6 +5367,31 @@ error = "사용자 상태 업데이트에 실패했습니다" success = "사용자를 성공적으로 삭제했습니다" error = "사용자 삭제에 실패했습니다" +[workspace.people.changePassword] +action = "비밀번호 변경" +title = "비밀번호 변경" +subtitle = "다음 사용자의 비밀번호 업데이트" +newPassword = "새 비밀번호" +confirmPassword = "비밀번호 확인" +placeholder = "새 비밀번호를 입력하세요" +confirmPlaceholder = "새 비밀번호를 다시 입력하세요" +passwordRequired = "새 비밀번호를 입력해 주세요" +passwordMismatch = "비밀번호가 일치하지 않습니다" +generateRandom = "안전한 비밀번호 생성" +generatedPreview = "생성된 비밀번호:" +copyTooltip = "클립보드에 복사" +copiedToClipboard = "비밀번호를 클립보드에 복사했습니다" +copyFailed = "비밀번호 복사 실패" +sendEmail = "이 변경 사항을 사용자에게 이메일로 알리기" +includePassword = "이메일에 새 비밀번호 포함" +forcePasswordChange = "다음 로그인 시 비밀번호 변경 강제" +emailUnavailable = "이 사용자의 이메일이 올바른 주소가 아니므로 알림이 비활성화되었습니다." +smtpDisabled = "이메일 알림을 사용하려면 설정에서 SMTP를 활성화해야 합니다." +notifyOnly = "비밀번호 없이 이메일이 발송되며, 관리자가 변경했음을 사용자에게 알려 줍니다." +submit = "비밀번호 업데이트" +success = "비밀번호가 성공적으로 업데이트되었습니다" +error = "비밀번호 업데이트에 실패했습니다" + [workspace.people.emailInvite] tab = "이메일 초대" description = "아래에 이메일을 쉼표로 구분해 입력하거나 붙여넣으세요. 사용자는 이메일로 로그인 자격 증명을 받습니다." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "이메일 주소를 최소 한 개 이상 입력해야 합니다" submit = "초대장 보내기" success = "사용자 초대가 완료되었습니다" -partialSuccess = "일부 초대가 실패했습니다" +partialFailure = "일부 초대가 실패했습니다" allFailed = "사용자 초대에 실패했습니다" error = "초대장 전송에 실패했습니다" @@ -5770,6 +5925,7 @@ subtitle = "Stirling 계정으로 로그인" [setup.selfhosted] title = "서버에 로그인" subtitle = "서버 자격 증명을 입력하세요" +link = "또는 셀프 호스팅 계정에 연결" [setup.server] title = "서버에 연결" @@ -5788,6 +5944,14 @@ description = "자가 호스팅 Stirling PDF 서버의 전체 URL을 입력하 emptyUrl = "서버 URL을 입력하세요" unreachable = "서버에 연결할 수 없습니다" testFailed = "연결 테스트에 실패했습니다" +configFetch = "서버 구성을 가져오지 못했습니다. URL을 확인하고 다시 시도하세요." + +[setup.server.error.securityDisabled] +title = "로그인이 활성화되어 있지 않음" +body = "이 서버에는 로그인이 활성화되어 있지 않습니다. 이 서버에 연결하려면 인증을 활성화해야 합니다:" +step1 = "환경에서 DOCKER_ENABLE_SECURITY=true를 설정하세요" +step2 = "또는 settings.yml에서 security.enableLogin=true를 설정하세요" +step3 = "서버를 재시작하세요" [setup.login] title = "로그인" @@ -5797,6 +5961,13 @@ submit = "로그인" signInWith = "다음으로 로그인" oauthPending = "인증을 위해 브라우저를 여는 중..." orContinueWith = "또는 이메일로 계속" +serverRequirement = "참고: 서버에서 로그인 기능이 활성화되어 있어야 합니다." +showInstructions = "활성화 방법" +hideInstructions = "지침 숨기기" +instructions = "Stirling PDF 서버에서 로그인 기능을 활성화하려면:" +instructionsEnvVar = "다음 환경 변수를 설정하세요:" +instructionsOrYml = "또는 settings.yml에서:" +instructionsRestart = "그런 다음 변경 사항을 적용하려면 서버를 재시작하세요." [setup.login.username] label = "사용자 이름" @@ -5853,6 +6024,7 @@ earlyAccess = "얼리 액세스" reset = "변경 사항 초기화" downloadJson = "JSON 다운로드" generatePdf = "PDF 생성" +saveChanges = "변경 사항 저장" [pdfTextEditor.options.autoScaleText] title = "텍스트 자동 크기 조정" @@ -5890,6 +6062,8 @@ alpha = "이 알파 뷰어는 아직 발전 중입니다—일부 글꼴, 색상 [pdfTextEditor.empty] title = "로드된 문서 없음" subtitle = "텍스트 편집을 시작하려면 PDF 또는 JSON 파일을 로드하세요." +dropzone = "여기에 PDF 또는 JSON 파일을 끌어다 놓거나 클릭하여 찾아보세요" +dropzoneWithFiles = "파일 탭에서 파일을 선택하거나, 여기에 PDF 또는 JSON 파일을 끌어다 놓거나 클릭하여 찾아보세요" [pdfTextEditor.welcomeBanner] title = "PDF 텍스트 편집기(얼리 액세스)에 오신 것을 환영합니다" diff --git a/frontend/public/locales/ml-ML/translation.toml b/frontend/public/locales/ml-ML/translation.toml index b7f97c3c2..2da56e0fc 100644 --- a/frontend/public/locales/ml-ML/translation.toml +++ b/frontend/public/locales/ml-ML/translation.toml @@ -131,7 +131,7 @@ unsupported = "പിന്തുണയില്ല" [toolPanel] placeholder = "തുടങ്ങാൻ ഒരു ടൂൾ തിരഞ്ഞെടുക്കുക" -alpha = "Alpha" +alpha = "ആൽഫ" premiumFeature = "പ്രീമിയം ഫീച്ചർ:" comingSoon = "വരുന്നു:" @@ -163,6 +163,11 @@ unfavorite = "പ്രിയപ്പെട്ടവയിൽ നിന്ന fullscreen = "ഫുൾസ്ക്രീൻ മോഡിലേക്കു മാറ്റുക" sidebar = "സൈഡ്ബാർ മോഡിലേക്കു മാറ്റുക" +[backendStartup] +notFoundTitle = "ബാക്ക്‌എൻഡ് കണ്ടെത്താനായില്ല" +retry = "വീണ്ടും ശ്രമിക്കുക" +unreachable = "ഈ ആപ്പ്ലിക്കേഷൻ നിലവിൽ ബാക്ക്‌എൻഡുമായി കണക്റ്റ് ചെയ്യാൻ കഴിയുന്നില്ല. ബാക്ക്‌എൻഡിന്റെ നിലയും നെറ്റ്‌വർക്ക് കണക്റ്റിവിറ്റിയും പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക." + [zipWarning] title = "വലിയ ZIP ഫയൽ" message = "ഈ ZIP-ൽ {{count}} ഫയലുകൾ ഉണ്ട്. എങ്കിലും എക്സ്ട്രാക്റ്റ് ചെയ്യട്ടേ?" @@ -369,7 +374,7 @@ privacy = "സ്വകാര്യത" [settings.developer] title = "ഡെവലപ്പർ" -apiKeys = "API Keys" +apiKeys = "API കീകൾ" [settings.tooltips] enableLoginFirst = "ആദ്യം ലോഗിൻ മോഡ് സജീവമാക്കുക" @@ -709,7 +714,7 @@ title = "പരത്തുക" desc = "ഒരു PDF-ൽ നിന്ന് എല്ലാ ഇന്ററാക്ടീവ് ഘടകങ്ങളും ഫോമുകളും നീക്കം ചെയ്യുക" [home.certSign] -tags = "authenticate,PEM,P12,official,encrypt,sign,certificate,PKCS12,JKS,server,manual,auto" +tags = "പ്രാമാണീകരണം,PEM,P12,ഔദ്യോഗികം,എൻക്രിപ്റ്റ്,സൈൻ,സർട്ടിഫിക്കറ്റ്,PKCS12,JKS,സെർവർ,മാനുവൽ,ഓട്ടോ" title = "സർട്ടിഫിക്കറ്റ് ഉപയോഗിച്ച് ഒപ്പിടുക" desc = "ഒരു സർട്ടിഫിക്കറ്റ്/കീ (PEM/P12) ഉപയോഗിച്ച് ഒരു PDF ഒപ്പിടുന്നു" @@ -794,7 +799,7 @@ title = "ഒരൊറ്റ വലിയ പേജ്" desc = "എല്ലാ PDF പേജുകളും ഒരൊറ്റ വലിയ പേജിലേക്ക് ലയിപ്പിക്കുന്നു" [home.showJS] -tags = "javascript,code,script" +tags = "javascript,കോഡ്,സ്ക്രിപ്റ്റ്" title = "ജാവാസ്ക്രിപ്റ്റ് കാണിക്കുക" desc = "ഒരു PDF-ൽ കുത്തിവച്ച ഏതെങ്കിലും JS തിരയുകയും പ്രദർശിപ്പിക്കുകയും ചെയ്യുന്നു" @@ -912,9 +917,12 @@ desc = "PDF പ്രവർത്തനങ്ങൾ ബന്ധിപ്പി desc = "മറ്റൊരു PDF-ന് മുകളിൽ PDF-കൾ ഓവർലേ ചെയ്യുന്നു" title = "PDF-കൾ ഓവർലേ ചെയ്യുക" +[home.pdfTextEditor] +title = "PDF ടെക്സ്റ്റ് എഡിറ്റർ" +desc = "PDFകളിലെ നിലവിലുള്ള ടെക്സ്റ്റും ചിത്രങ്ങളും തിരുത്തുക" [home.addText] -tags = "text,annotation,label" +tags = "ടെക്സ്റ്റ്,അനോട്ടേഷൻ,ലേബൽ" title = "ടെക്സ്റ്റ് ചേർക്കുക" desc = "നിങ്ങളുടെ PDF-ൽ എവിടെയിലും കസ്റ്റം ടെക്സ്റ്റ് ചേർക്കുക" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "വരച്ച ഒപ്പ്" defaultImageLabel = "അപ്‌ലോഡ് ചെയ്ത ഒപ്പ്" defaultTextLabel = "ടൈപ്പ് ചെയ്ത ഒപ്പ്" saveButton = "ഒപ്പ് സേവ് ചെയ്യുക" +savePersonal = "വ്യക്തിപരമായി സംരക്ഷിക്കുക" +saveShared = "പങ്കിട്ടതായി സംരക്ഷിക്കുക" saveUnavailable = "സേവ് ചെയ്യാൻ ആദ്യം ഒരു ഒപ്പ് സൃഷ്ടിക്കുക." noChanges = "നിലവിലെ ഒപ്പ് ഇതിനകം സേവ് ചെയ്തിട്ടുണ്ട്." +tempStorageTitle = "താൽക്കാലിക ബ്രൗസർ സ്റ്റോറേജ്" +tempStorageDescription = "ഒപ്പുകൾ നിങ്ങളുടെ ബ്രൗസറിൽ മാത്രം സംഭരിക്കപ്പെടും. ബ്രൗസർ ഡാറ്റ നീക്കം ചെയ്താൽ അല്ലെങ്കിൽ ബ്രൗസർ മാറ്റിയാൽ അവ നഷ്ടപ്പെടും." +personalHeading = "വ്യക്തിഗത ഒപ്പുകൾ" +sharedHeading = "പങ്കിട്ട ഒപ്പുകൾ" +personalDescription = "ഈ ഒപ്പുകൾ നിങ്ങള്ക്ക് മാത്രമേ കാണാനാകൂ." +sharedDescription = "എല്ലാ ഉപയോക്താക്കളും ഈ ഒപ്പുകൾ കാണുകയും ഉപയോഗിക്കുകയും ചെയ്യാം." [sign.saved.type] canvas = "ഡ്രോയിംഗ്" @@ -2731,7 +2747,7 @@ submit = "സമർപ്പിക്കുക" failed = "മൾട്ടി-പേജ് ലേഔട്ട് സൃഷ്ടിക്കുമ്പോൾ പിശക് സംഭവിച്ചു." [bookletImposition] -tags = "booklet,imposition,printing,binding,folding,signature" +tags = "ബുക്ക്‌ലെറ്റ്,ഇംപോസിഷൻ,പ്രിന്റിംഗ്,ബൈൻഡിംഗ്,മടക്കൽ,സിഗ്നേച്ചർ" title = "ബുക്ക്ലെറ്റ് ഇംപോസിഷൻ" header = "ബുക്ക്ലെറ്റ് ഇംപോസിഷൻ" submit = "ബുക്ക്ലെറ്റ് സൃഷ്ടിക്കുക" @@ -2830,7 +2846,7 @@ scaleFactor = "ഒരു പേജിന്റെ സൂം നില (ക്ര submit = "സമർപ്പിക്കുക" [adjustPageScale] -tags = "resize,modify,dimension,adapt" +tags = "വലിപ്പമാറ്റം,ഭേദഗതി,പരിമാണം,അനുസൃതമാക്കൽ" title = "പേജ് സ്കെയിൽ ക്രമപ്പെടുത്തുക" header = "പേജ് സ്കെയിൽ ക്രമപ്പെടുത്തുക" submit = "പേജ് സ്കെയിൽ ക്രമപ്പെടുത്തുക" @@ -2841,8 +2857,8 @@ label = "സ്കെയിൽ ഫാക്ടർ" [adjustPageScale.pageSize] label = "ടാർഗറ്റ് പേജ് വലിപ്പം" keep = "അസൽ വലിപ്പം നിലനിർത്തുക" -letter = "Letter" -legal = "Legal" +letter = "ലറ്റർ" +legal = "ലീഗൽ" [adjustPageScale.error] failed = "പേജ് സ്കെയിൽ ക്രമപ്പെടുത്തുന്നതിനിടെ പിശക് സംഭവിച്ചു." @@ -3020,6 +3036,91 @@ title = "PDF-നെക്കുറിച്ചുള്ള വിവരങ്ങ header = "PDF-നെക്കുറിച്ചുള്ള വിവരങ്ങൾ നേടുക" submit = "വിവരങ്ങൾ നേടുക" downloadJson = "JSON ഡൗൺലോഡ് ചെയ്യുക" +processing = "വിവരങ്ങൾ പുറത്തെടുക്കുന്നു..." +results = "ഫലങ്ങൾ" +noResults = "ഒരു റിപ്പോർട്ട് സൃഷ്ടിക്കാൻ ഉപകരണം പ്രവർത്തിപ്പിക്കുക." +downloads = "ഡൗൺലോഡുകൾ" +noneDetected = "ഒന്നും കണ്ടെത്തിയില്ല" +indexTitle = "സൂചിക" + +[getPdfInfo.report] +entryLabel = "പൂർണ്ണ വിവര സംഗ്രഹം" +shortTitle = "PDF വിവരം" + +[getPdfInfo.sections] +metadata = "മെറ്റാഡാറ്റ" +formFields = "ഫോം ഫീൽഡുകൾ" +basicInfo = "അടിസ്ഥാന വിവരം" +documentInfo = "പ്രമാണ വിവരം" +compliance = "അനുസരണം" +encryption = "എൻക്രിപ്ഷൻ" +permissions = "അനുമതികൾ" +other = "മറ്റ്" +perPageInfo = "ഓരോ പേജിലെ വിവരം" +tableOfContents = "വിഷയസൂചിക" + +[getPdfInfo.other] +attachments = "അറ്റാച്ച്മെന്റുകൾ" +embeddedFiles = "എംബെഡ് ചെയ്ത ഫയലുകൾ" +javaScript = "JavaScript" +layers = "ലെയറുകൾ" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "വലുപ്പം" +annotations = "അനോട്ടേഷനുകൾ" +images = "ചിത്രങ്ങൾ" +links = "ലിങ്കുകൾ" +fonts = "ഫോണ്ടുകൾ" +xobjects = "XObject എണ്ണങ്ങൾ" +multimedia = "മൾട്ടിമീഡിയ" + +[getPdfInfo.summary] +pages = "പേജുകൾ" +fileSize = "ഫയൽ വലുപ്പം" +pdfVersion = "PDF പതിപ്പ്" +language = "ഭാഷ" +title = "PDF സംഗ്രഹം" +author = "രചയിതാവ്" +created = "സൃഷ്ടിച്ചത്" +modified = "തിരുത്തിയത്" +permsAll = "എല്ലാ അനുമതികളും അനുവദിച്ചിട്ടുണ്ട്" +permsRestricted = "{{count}} നിയന്ത്രണങ്ങൾ" +permsMixed = "ചില അനുമതികൾ നിയന്ത്രിച്ചിരിക്കുന്നു" +hasCompliance = "അനുസരണ മാനദണ്ഡങ്ങൾ ഉണ്ട്" +noCompliance = "അനുസരണ മാനദണ്ഡങ്ങളില്ല" +basic = "അടിസ്ഥാന വിവരം" +documentInfo = "പ്രമാണ വിവരം" +securityTitle = "സുരക്ഷാ നില" +technical = "സാങ്കേതിക" +overviewTitle = "PDF അവലോകനം" + +[getPdfInfo.summary.security] +encrypted = "എൻക്രിപ്റ്റ് ചെയ്ത PDF - പാസ്‌വേഡ് സംരക്ഷണം ഉണ്ട്" +unencrypted = "എൻക്രിപ്റ്റ് ചെയ്യാത്ത PDF - പാസ്‌വേഡ് സംരക്ഷണമില്ല" + +[getPdfInfo.summary.tech] +images = "ചിത്രങ്ങൾ" +fonts = "ഫോണ്ടുകൾ" +formFields = "ഫോം ഫീൽഡുകൾ" +embeddedFiles = "എംബെഡ് ചെയ്ത ഫയലുകൾ" +javaScript = "JavaScript" +layers = "ലെയറുകൾ" +bookmarks = "ബുക്ക്‌മാർക്കുകൾ" +multimedia = "മൾട്ടിമീഡിയ" + +[getPdfInfo.summary.overview] +untitled = "ഒരു ശീർഷകമില്ലാത്ത പ്രമാണം" +unknown = "അജ്ഞാത രചയിതാവ്" +text = "ഇത് {{author}} സൃഷ്ടിച്ച, {{title}} എന്ന ശീർഷകമുള്ള {{pages}}-പേജ് PDF ആണ് (PDF പതിപ്പ് {{version}})." + +[getPdfInfo.error] +partial = "ചില ഫയലുകൾ പ്രോസസ്സ് ചെയ്യാൻ കഴിഞ്ഞില്ല." +unexpected = "പുറത്തെടുക്കുന്നതിനിടെ അപ്രതീക്ഷിത പിശക്." + +[getPdfInfo.status] +complete = "പുറത്തെടുക്കൽ പൂർത്തിയായി" [extractPage] tags = "വേർതിരിച്ചെടുക്കുക" @@ -3380,7 +3481,7 @@ certHint = "കസ്റ്റം ട്രസ്റ്റ് സോഴ്‌സ title = "സ്ഥിരീകരണ സെറ്റിങ്ങുകൾ" [replaceColor] -tags = "Replace Colour,Page operations,Back end,server side" +tags = "നിറം പകരുക,പേജ് പ്രവർത്തനങ്ങൾ,ബാക്ക്‌എൻഡ്,സെർവർ-സൈഡ്" [replaceColor.labels] settings = "സെറ്റിങ്ങുകൾ" @@ -3438,6 +3539,9 @@ signinTitle = "ദയവായി സൈൻ ഇൻ ചെയ്യുക" ssoSignIn = "സിംഗിൾ സൈൻ-ഓൺ വഴി ലോഗിൻ ചെയ്യുക" oAuth2AutoCreateDisabled = "OAUTH2 ഓട്ടോ-ക്രിയേറ്റ് യൂസർ പ്രവർത്തനരഹിതമാക്കി" oAuth2AdminBlockedUser = "രജിസ്റ്റർ ചെയ്യാത്ത ഉപയോക്താക്കളുടെ രജിസ്ട്രേഷനോ ലോഗിൻ ചെയ്യുന്നതോ നിലവിൽ തടഞ്ഞിരിക്കുന്നു. ദയവായി അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക." +oAuth2RequiresLicense = "OAuth/SSO ലോഗിനിന് ഒരു പെയ്ഡ് ലൈസൻസ് (Server അല്ലെങ്കിൽ Enterprise) ആവശ്യമാണ്. നിങ്ങളുടെ പ്ലാൻ അപ്‌ഗ്രേഡ് ചെയ്യാൻ അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക." +saml2RequiresLicense = "SAML ലോഗിനിന് ഒരു പെയ്ഡ് ലൈസൻസ് (Server അല്ലെങ്കിൽ Enterprise) ആവശ്യമാണ്. നിങ്ങളുടെ പ്ലാൻ അപ്‌ഗ്രേഡ് ചെയ്യാൻ അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക." +maxUsersReached = "നിലവിലുള്ള ലൈസൻസിലെ പരമാവധി ഉപയോക്താക്കൾ എത്തിച്ചേർന്നു. നിങ്ങളുടെ പ്ലാൻ അപ്‌ഗ്രേഡ് ചെയ്യുകയോ കൂടുതൽ സീറ്റുകൾ ചേർക്കുകയോ ചെയ്യാൻ അഡ്മിനിസ്ട്രേറ്ററുമായി ബന്ധപ്പെടുക." oauth2RequestNotFound = "അംഗീകാര അഭ്യർത്ഥന കണ്ടെത്തിയില്ല" oauth2InvalidUserInfoResponse = "അസാധുവായ ഉപയോക്തൃ വിവര പ്രതികരണം" oauth2invalidRequest = "അസാധുവായ അഭ്യർത്ഥന" @@ -3771,7 +3875,7 @@ version = "നിലവിലെ റിലീസ്" title = "API ഡോക്യുമെന്റേഷൻ" header = "API ഡോക്യുമെന്റേഷൻ" desc = "Stirling PDF API എൻഡ്പോയിന്റുകൾ കാണുകയും പരിശോധിക്കുകയും ചെയ്യുക" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,ഡോക്യുമെന്റേഷൻ,swagger,എൻഡ്പോയിന്റുകൾ,വികസനം" [cookieBanner.popUp] title = "ഞങ്ങൾ കുക്കികൾ എങ്ങനെ ഉപയോഗിക്കുന്നു" @@ -3846,14 +3950,17 @@ fitToWidth = "വീതിക്ക് ഒത്താക്കുക" actualSize = "യഥാർത്ഥ വലിപ്പം" [viewer] +cannotPreviewFile = "ഫയൽ പ്രിവ്യൂ ചെയ്യാൻ കഴിയില്ല" +dualPageView = "രണ്ടുപേജ് ദൃശ്യം" firstPage = "ആദ്യ പേജ്" lastPage = "അവസാന പേജ്" -previousPage = "മുൻപത്തെ പേജ്" nextPage = "അടുത്ത പേജ്" +onlyPdfSupported = "വ്യൂവറിന് PDF ഫയലുകൾ മാത്രം പിന്തുണയ്ക്കാം. ഈ ഫയൽ വേറെ ഒരു ഫോർമാറ്റാണെന്ന് തോന്നുന്നു." +previousPage = "മുൻപത്തെ പേജ്" +singlePageView = "ഒറ്റ പേജ് ദൃശ്യം" +unknownFile = "അപരിചിതമായ ഫയൽ" zoomIn = "സൂം ഇൻ" zoomOut = "സൂം ഔട്ട്" -singlePageView = "ഒറ്റ പേജ് ദൃശ്യം" -dualPageView = "രണ്ടുപേജ് ദൃശ്യം" [rightRail] closeSelected = "തിരഞ്ഞെടുത്ത ഫയലുകൾ അടയ്‌ക്കുക" @@ -3877,6 +3984,7 @@ toggleSidebar = "സൈഡ്ബാർ മാറ്റുക" exportSelected = "തിരഞ്ഞെടുത്ത പേജുകൾ എക്സ്പോർട്ട് ചെയ്യുക" toggleAnnotations = "അനോട്ടേഷൻ ദൃശ്യമാനം മാറ്റുക" annotationMode = "അനോട്ടേഷൻ മോഡ് മാറ്റുക" +print = "PDF അച്ചടിക്കുക" draw = "വരയ്ക്കുക" save = "സംരക്ഷിക്കുക" saveChanges = "മാറ്റങ്ങൾ സംരക്ഷിക്കുക" @@ -4231,7 +4339,7 @@ label = "പ്രൊവൈഡർ" description = "ഓതന്റിക്കേഷനായി ഉപയോഗിക്കുന്ന OAuth2 പ്രൊവൈഡർ" [admin.settings.connections.oauth2.issuer] -label = "Issuer URL" +label = "ഇഷ്യൂവർ URL" description = "OAuth2 പ്രൊവൈഡറിന്റെ issuer URL" [admin.settings.connections.oauth2.clientId] @@ -4255,7 +4363,7 @@ label = "രജിസ്‌ട്രേഷൻ തടയുക" description = "OAuth2 വഴി പുതിയ ഉപയോക്തൃ രജിസ്‌ട്രേഷൻ തടയുക" [admin.settings.connections.oauth2.scopes] -label = "OAuth2 Scopes" +label = "OAuth2 സ്കോപ്പുകൾ" description = "OAuth2 സ്കോപ്പുകളുടെ കോമ ഉപയോഗിച്ച് വേർതിരിച്ച പട്ടിക (ഉദാ., openid, profile, email)" [admin.settings.connections.saml2] @@ -4494,6 +4602,7 @@ description = "Impressum-ലേക്ക് URL അല്ലെങ്കിൽ title = "പ്രീമിയം & എന്റർപ്രൈസ്" description = "നിങ്ങളുടെ പ്രീമിയം അല്ലെങ്കിൽ എന്റർപ്രൈസ് ലൈസൻസ് കീ ക്രമീകരിക്കുക." license = "ലൈസൻസ് കോൺഫിഗറേഷൻ" +noInput = "ദയവായി ഒരു ലൈസന്റ്‌സ് കീ അല്ലെങ്കിൽ ഫയൽ നൽകുക" [admin.settings.premium.licenseKey] toggle = "ലൈസൻസ് കീ അല്ലെങ്കിൽ സർട്ടിഫിക്കറ്റ് ഫയൽ ഉണ്ടോ?" @@ -4511,6 +4620,25 @@ line1 = "നിലവിലെ ലൈസൻസ് കീ ഓവർറൈറ് line2 = "നിങ്ങൾ മറ്റെവിടെയെങ്കിലും ബാക്കപ്പ് എടുത്തിട്ടില്ലെങ്കിൽ നിങ്ങളുടെ പഴയ ലൈസൻസ് സ്ഥിരമായി നഷ്ടപ്പെടും." line3 = "പ്രധാനപ്പെട്ടത്: ലൈസൻസ് കീകൾ സ്വകാര്യവും സുരക്ഷിതവുമാക്കി സൂക്ഷിക്കുക. ഒരിക്കലും അവ പൊതു വേദിയിൽ പങ്കുവെക്കരുത്." +[admin.settings.premium.inputMethod] +text = "ലൈസൻസ് കീ" +file = "സർട്ടിഫിക്കറ്റ് ഫയൽ" + +[admin.settings.premium.file] +label = "ലൈസൻസ് സർട്ടിഫിക്കറ്റ് ഫയൽ" +description = "നിങ്ങളുടെ ഓഫ്ലൈൻ വാങ്ങലുകളിൽ നിന്നുള്ള .lic അല്ലെങ്കിൽ .cert ലൈസൻസ് ഫയൽ അപ്‌ലോഡ് ചെയ്യുക" +choose = "ലൈസൻസ് ഫയൽ തിരഞ്ഞെടുക്കുക" +selected = "തിരഞ്ഞെടുക്കിയത്: {{filename}} ({{size}})" +successMessage = "ലൈസൻസ് ഫയൽ അപ്‌ലോഡ് ചെയ്തു വിജയകരമായി സജീവമാക്കിയിരിക്കുന്നു. റീസ്റ്റാർട്ട് ആവശ്യമില്ല." + +[admin.settings.premium.currentLicense] +title = "സജീവ ലൈസൻസ്" +file = "ഉറവിടം: ലൈസൻസ് ഫയൽ ({{path}})" +key = "ഉറവിടം: ലൈസൻസ് കീ" +type = "തരം: {{type}}" +noInput = "ദയവായി ഒരു ലൈസൻസ് കീ നൽകുകയോ ഒരു സർട്ടിഫിക്കറ്റ് ഫയൽ അപ്‌ലോഡ് ചെയ്യുകയോ ചെയ്യുക" +success = "വിജയം" + [admin.settings.premium.enabled] label = "പ్రీമിയം സവിശേഷതകൾ പ്രാപ്തമാക്കുക" description = "പ്രോ/എന്റർപ്രൈസ് സവിശേഷതകൾക്കായി ലൈസൻസ് കീ പരിശോധനകൾ പ്രാപ്തമാക്കുക" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} തിരഞ്ഞെടുക്കപ്പെട download = "ഡൗൺലോഡ്" delete = "ഇല്ലാതാക്കുക" unsupported = "പിന്തുണയില്ല" +active = "സജീവം" addToUpload = "അപ്‌ലോഡിലേക്ക് ചേർക്കുക" +closeFile = "ഫയൽ അടയ്ക്കുക" deleteAll = "എല്ലാം ഇല്ലാതാക്കുക" loadingFiles = "ഫയലുകൾ ലോഡുചെയ്യുന്നു..." noFiles = "ഫയലുകളൊന്നും ലഭ്യമല്ല" @@ -5132,7 +5262,7 @@ upgrade = "ഇപ്പോൾ അപ്‌ഗ്രേഡ് ചെയ്യു freeTitle = "സെർവർ ലൈസൻസ്" overLimitTitle = "സെർവർ ലൈസൻസ് ആവശ്യമാണ്" overLimitBody = "ഞങ്ങളുടെ ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി {{freeTierLimit}} ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. നിങ്ങള്ക്ക് {{overLimitUserCopy}} Stirling ഉപയോക്താക്കളുണ്ട്. തടസ്സമില്ലാതെ തുടരാൻ, Stirling Server പ്ലാനിലേക്ക് അപ്‌ഗ്രേഡ് ചെയ്യുക - unlimited seats, PDF text editing, പൂർണ്ണ അഡ്മിൻ നിയന്ത്രണം, $99/server/mo." -freeBody = "ഞങ്ങളുടെ Open-Core ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി {{freeTierLimit}} ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. തടസ്സമില്ലാതെ സ്കെയിൽ ചെയ്യാനും പുതിയ PDF text editing tool ന് മുൻകാല ആക്സസ് നേടാനും, Stirling Server പ്ലാൻ ഞങ്ങൾ ശുപാർശ ചെയ്യുന്നു - പൂർണ്ണ എഡിറ്റിംഗും unlimited seats ഉം $99/server/mo." +freeBody = "ഞങ്ങളുടെ Open-Core ലൈസൻസിംഗ് ഓരോ സെർവർക്കും പരമാവധി {{freeTierLimit}} ഉപയോക്താക്കളെ സൗജന്യമായി അനുവദിക്കുന്നു. തടസ്സമില്ലാതെ സ്‌കെയിൽ ചെയ്യാൻ, ഞങ്ങൾ Stirling Server പ്ലാൻ ശുപാർശ ചെയ്യുന്നു - പരിമിതിയില്ലാത്ത സീറ്റുകൾയും SSO പിന്തുണയും for $99/server/mo." [onboarding.desktopInstall] title = "ഡൗൺലോഡ്" @@ -5237,6 +5367,31 @@ error = "ഉപയോക്താവിന്റെ നില അപ്‌ഡേ success = "ഉപയോക്താവിനെ വിജയകരമായി ഇല്ലാതാക്കി" error = "ഉപയോക്താവിനെ ഇല്ലാതാക്കൽ പരാജയപ്പെട്ടു" +[workspace.people.changePassword] +action = "പാസ്‌വേഡ് മാറ്റുക" +title = "പാസ്‌വേഡ് മാറ്റുക" +subtitle = "ഇതിനായുള്ള പാസ്‌വേഡ് പുതുക്കുക" +newPassword = "പുതിയ പാസ്‌വേഡ്" +confirmPassword = "പാസ്‌വേഡ് ശരിവെക്കുക" +placeholder = "ഒരു പുതിയ പാസ്‌വേഡ് നൽകുക" +confirmPlaceholder = "പുതിയ പാസ്‌വേഡ് വീണ്ടും നൽകുക" +passwordRequired = "ദയവായി ഒരു പുതിയ പാസ്‌വേഡ് നൽകുക" +passwordMismatch = "പാസ്‌വേഡുകൾ പൊരുത്തപ്പെടുന്നില്ല" +generateRandom = "സുരക്ഷിത പാസ്‌വേഡ് സൃഷ്ടിക്കുക" +generatedPreview = "സൃഷ്ടിച്ച പാസ്‌വേഡ്:" +copyTooltip = "ക്ലിപ്പ്ബോർഡിലേയ്ക്ക് പകർത്തുക" +copiedToClipboard = "പാസ്‌വേഡ് ക്ലിപ്പ്ബോർഡിലേയ്ക്ക് പകർത്തി" +copyFailed = "പാസ്‌വേഡ് പകർത്തൽ പരാജയപ്പെട്ടു" +sendEmail = "ഈ മാറ്റത്തെക്കുറിച്ച് ഉപയോക്താവിന് ഇമെയിൽ അയയ്ക്കുക" +includePassword = "പുതിയ പാസ്‌വേഡ് ഇമെയിലിൽ ഉൾപ്പെടുത്തുക" +forcePasswordChange = "അടുത്ത ലോഗിനിൽ ഉപയോക്താവിനെ പാസ്‌വേഡ് മാറ്റാൻ നിർബന്ധിക്കുക" +emailUnavailable = "ഈ ഉപയോക്താവിന്റെ ഇമെയിൽ ഒരു സാധുവായ ഇമെയിൽ വിലാസമല്ല. അറിയിപ്പുകൾ പ്രവർത്തനരഹിതമാണ്." +smtpDisabled = "ഇമെയിൽ അറിയിപ്പുകൾക്കായി ക്രമീകരണങ്ങളിൽ SMTP സജീവമാക്കണം." +notifyOnly = "പാസ്‌വേഡ് ഇല്ലാതെ ഒരു ഇമെയിൽ അയയ്ക്കപ്പെടും; അഡ്മിൻ അത് മാറ്റിയതായി ഉപയോക്താവിനെ അറിയിക്കും." +submit = "പാസ്‌വേഡ് പുതുക്കുക" +success = "പാസ്‌വേഡ് വിജയകരമായി പുതുക്കി" +error = "പാസ്‌വേഡ് പുതുക്കൽ പരാജയപ്പെട്ടു" + [workspace.people.emailInvite] tab = "ഇമെയിൽ ക്ഷണം" description = "താഴെ ഇമെയിലുകൾ കോമ ഉപയോഗിച്ച് വേർതിരിച്ച് ടൈപ്പ് ചെയ്യുകയോ പതിക്കുകയോ ചെയ്യുക. ഉപയോക്താക്കൾക്ക് ലോഗിൻ വിവരങ്ങൾ ഇമെയിലിലൂടെ ലഭിക്കും." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "കുറഞ്ഞത് ഒരു ഇമെയിൽ വിലാസമെങ്കിലും ആവശ്യമാണ്" submit = "ക്ഷണങ്ങൾ അയയ്ക്കുക" success = "ഉപയോക്താക്കളെ വിജയകരമായി ക്ഷണിച്ചു" -partialSuccess = "ചില ക്ഷണങ്ങൾ പരാജയപ്പെട്ടു" +partialFailure = "ചില ക്ഷണങ്ങൾ പരാജയപ്പെട്ടു" allFailed = "ഉപയോക്താക്കളെ ക്ഷണിക്കൽ പരാജയപ്പെട്ടു" error = "ക്ഷണങ്ങൾ അയയ്ക്കൽ പരാജയപ്പെട്ടു" @@ -5770,6 +5925,7 @@ subtitle = "നിങ്ങളുടെ Stirling അക്കൗണ്ടില [setup.selfhosted] title = "സെർവറിൽ സൈൻ ഇൻ ചെയ്യുക" subtitle = "നിങ്ങളുടെ സെർവർ ക്രെഡൻഷ്യലുകൾ നൽകുക" +link = "അല്ലെങ്കിൽ സ്വയം-ഹോസ്റ്റുചെയ്ത അക്കൗണ്ടുമായി ബന്ധിപ്പിക്കുക" [setup.server] title = "സെർവറിലേക്ക് കണക്റ്റുചെയ്യുക" @@ -5788,6 +5944,14 @@ description = "നിങ്ങളുടെ സെൽഫ്-ഹോസ്റ്റ emptyUrl = "ദയവായി ഒരു സെർവർ URL നൽകുക" unreachable = "സെർവറുമായി ബന്ധപ്പെടാൻ കഴിഞ്ഞില്ല" testFailed = "കണക്ഷൻ ടെസ്റ്റ് പരാജയപ്പെട്ടു" +configFetch = "സർവർ കോൺഫിഗറേഷൻ ലഭ്യമാക്കൽ പരാജയപ്പെട്ടു. ദയവായി URL പരിശോധിച്ച് വീണ്ടും ശ്രമിക്കുക." + +[setup.server.error.securityDisabled] +title = "ലോഗിൻ സജീവമാക്കിയിട്ടില്ല" +body = "ഈ സർവറിൽ ലോഗിൻ സജീവമാക്കിയിട്ടില്ല. ഈ സർവറുമായി ബന്ധപ്പെടാൻ, ഓതന്റിക്കേഷൻ സജീവമാക്കണം:" +step1 = "നിങ്ങളുടെ പരിസ്ഥിതിയിൽ DOCKER_ENABLE_SECURITY=true ആയി ക്രമീകരിക്കുക" +step2 = "അല്ലെങ്കിൽ settings.yml ൽ security.enableLogin=true ആയി ക്രമീകരിക്കുക" +step3 = "സർവർ പുനരാരംഭിക്കുക" [setup.login] title = "സൈൻ ഇൻ" @@ -5797,6 +5961,13 @@ submit = "ലോഗിൻ" signInWith = "ഇതുപയോഗിച്ച് സൈൻ ഇൻ ചെയ്യുക" oauthPending = "ഓതന്റിക്കേഷനായി ബ്രൗസർ തുറക്കുന്നു..." orContinueWith = "അല്ലെങ്കിൽ ഇമെയിലോടെ തുടരുക" +serverRequirement = "ശ്രദ്ധിക്കുക: സെർവറിൽ ലോഗിൻ പ്രവർത്തനക്ഷമമാക്കിയിരിക്കണം." +showInstructions = "എങ്ങനെ പ്രവർത്തനക്ഷമമാക്കാം?" +hideInstructions = "നിർദ്ദേശങ്ങൾ മറയ്ക്കുക" +instructions = "നിങ്ങളുടെ Stirling PDF സെർവറിൽ ലോഗിൻ പ്രവർത്തനക്ഷമമാക്കാൻ:" +instructionsEnvVar = "Environment variable സജ്ജമാക്കുക:" +instructionsOrYml = "അല്ലെങ്കിൽ settings.yml-ൽ:" +instructionsRestart = "തുടർന്ന് മാറ്റങ്ങൾ പ്രാബല്യത്തിൽ വരാൻ നിങ്ങളുടെ സെർവർ റീസ്റ്റാർട്ട് ചെയ്യുക." [setup.login.username] label = "യൂസർനെയിം" @@ -5847,12 +6018,13 @@ singleLine = "സിംഗിൾ ലൈൻ" [pdfTextEditor.badges] unsaved = "എഡിറ്റ് ചെയ്തു" modified = "എഡിറ്റ് ചെയ്തു" -earlyAccess = "Early Access" +earlyAccess = "എർലി ആക്‌സസ്" [pdfTextEditor.actions] reset = "മാറ്റങ്ങൾ റീസെറ്റ് ചെയ്യുക" downloadJson = "JSON ഡൗൺലോഡ് ചെയ്യുക" generatePdf = "PDF സൃഷ്ടിക്കുക" +saveChanges = "മാറ്റങ്ങൾ സംരക്ഷിക്കുക" [pdfTextEditor.options.autoScaleText] title = "ബോക്‌സിൽ ഒതുങ്ങാൻ ടെക്സ്റ്റ് സ്വയം സ്കെയിൽ ചെയ്യുക" @@ -5890,6 +6062,8 @@ alpha = "ഈ ആൽഫ വീവർ ഇനിയും വികസനത്ത [pdfTextEditor.empty] title = "പ്രമാണം ലോഡ് ചെയ്തിട്ടില്ല" subtitle = "ടെക്സ്റ്റ് എഡിറ്റ് ആരംഭിക്കാൻ PDF അല്ലെങ്കിൽ JSON ഫയൽ ലോഡ് ചെയ്യുക." +dropzone = "ഒരു PDF അല്ലെങ്കിൽ JSON ഫയൽ ഇവിടെ വലിച്ചിടുക, അല്ലെങ്കിൽ ബ്രൗസ് ചെയ്യാൻ ക്ലിക്ക് ചെയ്യുക" +dropzoneWithFiles = "Files ടാബിൽ നിന്ന് ഒരു ഫയൽ തിരഞ്ഞെടുക്കുക, അല്ലെങ്കിൽ ഒരു PDF അല്ലെങ്കിൽ JSON ഫയൽ ഇവിടെ വലിച്ചിടുക, അല്ലെങ്കിൽ ബ്രൗസ് ചെയ്യാൻ ക്ലിക്ക് ചെയ്യുക" [pdfTextEditor.welcomeBanner] title = "PDF Text Editor-ലേക്ക് സ്വാഗതം (Early Access)" diff --git a/frontend/public/locales/nl-NL/translation.toml b/frontend/public/locales/nl-NL/translation.toml index 321acd475..b9599e127 100644 --- a/frontend/public/locales/nl-NL/translation.toml +++ b/frontend/public/locales/nl-NL/translation.toml @@ -131,7 +131,7 @@ unsupported = "Niet ondersteund" [toolPanel] placeholder = "Kies een tool om te beginnen" -alpha = "Alpha" +alpha = "Alfa" premiumFeature = "Premiumfunctie:" comingSoon = "Binnenkort beschikbaar:" @@ -163,6 +163,11 @@ unfavorite = "Uit favorieten verwijderen" fullscreen = "Overschakelen naar volledig scherm" sidebar = "Overschakelen naar zijbalkmodus" +[backendStartup] +notFoundTitle = "Backend niet gevonden" +retry = "Opnieuw proberen" +unreachable = "De applicatie kan momenteel geen verbinding maken met de backend. Controleer de status van de backend en de netwerkverbinding en probeer het vervolgens opnieuw." + [zipWarning] title = "Groot ZIP-bestand" message = "Dit ZIP-bestand bevat {{count}} bestanden. Toch uitpakken?" @@ -347,7 +352,7 @@ teams = "Teams" title = "Configuratie" systemSettings = "Systeeminstellingen" features = "Functies" -endpoints = "Endpoints" +endpoints = "Eindpunten" database = "Database" advanced = "Geavanceerd" @@ -556,7 +561,7 @@ totalEndpoints = "Totaal aantal endpoints" totalVisits = "Totaal aantal bezoeken" showing = "Weergeven" selectedVisits = "Geselecteerde bezoeken" -endpoint = "Endpoint" +endpoint = "Eindpunt" visits = "Bezoeken" percentage = "Percentage" loading = "Laden..." @@ -912,6 +917,9 @@ desc = "Bouw workflows met meerdere stappen door PDF-acties te koppelen. Ideaal desc = "Plaatst PDF's over een andere PDF heen" title = "PDF's overlappen" +[home.pdfTextEditor] +title = "PDF-teksteditor" +desc = "Bewerk bestaande tekst en afbeeldingen in PDF's" [home.addText] tags = "tekst,annotatie,label" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Getekende handtekening" defaultImageLabel = "Geüploade handtekening" defaultTextLabel = "Getypte handtekening" saveButton = "Handtekening opslaan" +savePersonal = "Als persoonlijk opslaan" +saveShared = "Als gedeeld opslaan" saveUnavailable = "Maak eerst een handtekening om deze op te slaan." noChanges = "De huidige handtekening is al opgeslagen." +tempStorageTitle = "Tijdelijke browseropslag" +tempStorageDescription = "Handtekeningen worden alleen in je browser opgeslagen. Ze gaan verloren als je je browsergegevens wist of van browser wisselt." +personalHeading = "Persoonlijke handtekeningen" +sharedHeading = "Gedeelde handtekeningen" +personalDescription = "Alleen jij kunt deze handtekeningen zien." +sharedDescription = "Alle gebruikers kunnen deze handtekeningen zien en gebruiken." [sign.saved.type] canvas = "Tekening" @@ -3020,6 +3036,91 @@ title = "Informatie over PDF ophalen" header = "Informatie over PDF ophalen" submit = "Haal informatie op" downloadJson = "JSON downloaden" +processing = "Informatie wordt geëxtraheerd..." +results = "Resultaten" +noResults = "Voer de tool uit om een rapport te genereren." +downloads = "Downloads" +noneDetected = "Niets gedetecteerd" +indexTitle = "Index" + +[getPdfInfo.report] +entryLabel = "Volledig informatieoverzicht" +shortTitle = "PDF-informatie" + +[getPdfInfo.sections] +metadata = "Metadata" +formFields = "Formuliervelden" +basicInfo = "Basisinformatie" +documentInfo = "Documentinformatie" +compliance = "Compliance" +encryption = "Versleuteling" +permissions = "Machtigingen" +other = "Overig" +perPageInfo = "Informatie per pagina" +tableOfContents = "Inhoudsopgave" + +[getPdfInfo.other] +attachments = "Bijlagen" +embeddedFiles = "Ingesloten bestanden" +javaScript = "JavaScript" +layers = "Lagen" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Grootte" +annotations = "Annotaties" +images = "Afbeeldingen" +links = "Koppelingen" +fonts = "Lettertypen" +xobjects = "Aantal XObjects" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Pagina's" +fileSize = "Bestandsgrootte" +pdfVersion = "PDF-versie" +language = "Taal" +title = "PDF-samenvatting" +author = "Auteur" +created = "Gemaakt" +modified = "Gewijzigd" +permsAll = "Alle machtigingen toegestaan" +permsRestricted = "{{count}} beperkingen" +permsMixed = "Sommige machtigingen beperkt" +hasCompliance = "Voldoet aan compliancestandaarden" +noCompliance = "Geen compliancestandaarden" +basic = "Basisinformatie" +documentInfo = "Documentinformatie" +securityTitle = "Beveiligingsstatus" +technical = "Technisch" +overviewTitle = "PDF-overzicht" + +[getPdfInfo.summary.security] +encrypted = "Versleutelde PDF - Wachtwoordbeveiliging aanwezig" +unencrypted = "Onversleutelde PDF - Geen wachtwoordbeveiliging" + +[getPdfInfo.summary.tech] +images = "Afbeeldingen" +fonts = "Lettertypen" +formFields = "Formuliervelden" +embeddedFiles = "Ingesloten bestanden" +javaScript = "JavaScript" +layers = "Lagen" +bookmarks = "Bladwijzers" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "een document zonder titel" +unknown = "Onbekende auteur" +text = "Dit is een PDF van {{pages}} pagina's met de titel {{title}}, gemaakt door {{author}} (PDF-versie {{version}})." + +[getPdfInfo.error] +partial = "Sommige bestanden konden niet worden verwerkt." +unexpected = "Onverwachte fout tijdens het extraheren." + +[getPdfInfo.status] +complete = "Extractie voltooid" [extractPage] tags = "extraheren" @@ -3438,6 +3539,9 @@ signinTitle = "Gelieve in te loggen" ssoSignIn = "Inloggen via Single Sign-on" oAuth2AutoCreateDisabled = "OAUTH2 Automatisch aanmaken gebruiker uitgeschakeld" oAuth2AdminBlockedUser = "Registratie of inloggen van niet-registreerde gebruikers is helaas momenteel geblokkeerd. Neem contact op met de beheerder." +oAuth2RequiresLicense = "OAuth/SSO-inloggen vereist een betaalde licentie (Server of Enterprise). Neem contact op met de beheerder om uw abonnement te upgraden." +saml2RequiresLicense = "SAML-inloggen vereist een betaalde licentie (Server of Enterprise). Neem contact op met de beheerder om uw abonnement te upgraden." +maxUsersReached = "Het maximumaantal gebruikers voor uw huidige licentie is bereikt. Neem contact op met de beheerder om uw abonnement te upgraden of extra plaatsen toe te voegen." oauth2RequestNotFound = "Autorisatieverzoek niet gevonden" oauth2InvalidUserInfoResponse = "Ongeldige reactie op gebruikersinfo" oauth2invalidRequest = "Ongeldig verzoek" @@ -3805,7 +3909,7 @@ description = "These cookies are essential for the website to function properly. 2 = "Altijd ingeschakeld" [cookieBanner.preferencesModal.analytics] -title = "Analytics" +title = "Analyse" description = "Deze cookies helpen ons te begrijpen hoe onze tools worden gebruikt, zodat we ons kunnen richten op het bouwen van de functies die onze community het meest waardeert. Wees gerust—Stirling PDF kan niet en zal nooit de inhoud van de documenten waarmee je werkt volgen." [cookieBanner.services] @@ -3846,14 +3950,17 @@ fitToWidth = "Passend op breedte" actualSize = "Werkelijke grootte" [viewer] +cannotPreviewFile = "Kan voorbeeld van bestand niet weergeven" +dualPageView = "Dubbele paginaweergave" firstPage = "Eerste pagina" lastPage = "Laatste pagina" -previousPage = "Vorige pagina" nextPage = "Volgende pagina" +onlyPdfSupported = "De viewer ondersteunt alleen PDF-bestanden. Dit bestand lijkt een ander formaat te hebben." +previousPage = "Vorige pagina" +singlePageView = "Enkele paginaweergave" +unknownFile = "Onbekend bestand" zoomIn = "Inzoomen" zoomOut = "Uitzoomen" -singlePageView = "Enkele paginaweergave" -dualPageView = "Dubbele paginaweergave" [rightRail] closeSelected = "Geselecteerde bestanden sluiten" @@ -3877,6 +3984,7 @@ toggleSidebar = "Zijbalk tonen/verbergen" exportSelected = "Geselecteerde pagina's exporteren" toggleAnnotations = "Annotaties tonen/verbergen" annotationMode = "Annotatiemodus schakelen" +print = "PDF afdrukken" draw = "Tekenen" save = "Opslaan" saveChanges = "Wijzigingen opslaan" @@ -4343,7 +4451,7 @@ features = "Feature-flags" processing = "Verwerking" [admin.settings.advanced.endpoints] -label = "Endpoints" +label = "Eindpunten" manage = "API-endpoints beheren" description = "Endpointbeheer wordt geconfigureerd via YAML. Zie de documentatie voor details over het in-/uitschakelen van specifieke endpoints." @@ -4494,6 +4602,7 @@ description = "URL of bestandsnaam van het impressum (in sommige jurisdicties ve title = "Premium & Enterprise" description = "Configureer je premium- of enterprise-licentiesleutel." license = "Licentieconfiguratie" +noInput = "Geef een licentiesleutel of bestand op" [admin.settings.premium.licenseKey] toggle = "Heb je een licentiesleutel of certificaatbestand?" @@ -4511,6 +4620,25 @@ line1 = "Het overschrijven van je huidige licentiesleutel kan niet ongedaan word line2 = "Je vorige licentie gaat permanent verloren, tenzij je er elders een back-up van hebt." line3 = "Belangrijk: houd licentiesleutels privé en veilig. Deel ze nooit openbaar." +[admin.settings.premium.inputMethod] +text = "Licentiesleutel" +file = "Certificaatbestand" + +[admin.settings.premium.file] +label = "Licentiecertificaatbestand" +description = "Upload je .lic- of .cert-licentiebestand van offline aankopen" +choose = "Kies licentiebestand" +selected = "Geselecteerd: {{filename}} ({{size}})" +successMessage = "Licentiebestand succesvol geüpload en geactiveerd. Herstarten niet vereist." + +[admin.settings.premium.currentLicense] +title = "Actieve licentie" +file = "Bron: licentiebestand ({{path}})" +key = "Bron: licentiesleutel" +type = "Type: {{type}}" +noInput = "Geef een licentiesleutel op of upload een certificaatbestand" +success = "Succes" + [admin.settings.premium.enabled] label = "Premiumfuncties inschakelen" description = "Licentiesleutelcontrole inschakelen voor pro-/enterprise-functies" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} geselecteerd" download = "Downloaden" delete = "Verwijderen" unsupported = "Niet ondersteund" +active = "Actief" addToUpload = "Aan upload toevoegen" +closeFile = "Bestand sluiten" deleteAll = "Alles verwijderen" loadingFiles = "Bestanden laden..." noFiles = "Geen bestanden beschikbaar" @@ -5132,7 +5262,7 @@ upgrade = "Nu upgraden →" freeTitle = "Serverlicentie" overLimitTitle = "Serverlicentie vereist" overLimitBody = "Onze licentie staat tot {{freeTierLimit}} gebruikers gratis per server toe. Je hebt {{overLimitUserCopy}} Stirling-gebruikers. Om zonder onderbreking door te gaan, upgrade naar het Stirling Server-plan - onbeperkte plaatsen, PDF-tekstbewerking en volledige admincontrole voor $99/server/maand." -freeBody = "Onze Open-Core-licentie staat tot {{freeTierLimit}} gebruikers gratis per server toe. Om ononderbroken te schalen en vroege toegang te krijgen tot onze nieuwe PDF-tekstbewerkingstool, raden we het Stirling Server-plan aan - volledige bewerking en onbeperkte plaatsen voor $99/server/maand." +freeBody = "Onze Open-Core-licentie staat tot {{freeTierLimit}} gebruikers per server gratis toe. Om ononderbroken op te schalen, raden we het Stirling Server-abonnement aan - onbeperkte plaatsen en SSO-ondersteuning voor $99/server/maand." [onboarding.desktopInstall] title = "Downloaden" @@ -5237,6 +5367,31 @@ error = "Bijwerken van gebruikersstatus is mislukt" success = "Gebruiker succesvol verwijderd" error = "Gebruiker verwijderen is mislukt" +[workspace.people.changePassword] +action = "Wachtwoord wijzigen" +title = "Wachtwoord wijzigen" +subtitle = "Wachtwoord bijwerken voor" +newPassword = "Nieuw wachtwoord" +confirmPassword = "Wachtwoord bevestigen" +placeholder = "Voer een nieuw wachtwoord in" +confirmPlaceholder = "Voer het nieuwe wachtwoord opnieuw in" +passwordRequired = "Voer een nieuw wachtwoord in" +passwordMismatch = "Wachtwoorden komen niet overeen" +generateRandom = "Beveiligd wachtwoord genereren" +generatedPreview = "Gegenereerd wachtwoord:" +copyTooltip = "Kopiëren naar klembord" +copiedToClipboard = "Wachtwoord gekopieerd naar klembord" +copyFailed = "Kopiëren van wachtwoord mislukt" +sendEmail = "Gebruiker per e-mail informeren over deze wijziging" +includePassword = "Nieuw wachtwoord in de e-mail opnemen" +forcePasswordChange = "Gebruiker dwingen het wachtwoord bij de volgende aanmelding te wijzigen" +emailUnavailable = "Het e-mailadres van deze gebruiker is ongeldig. Meldingen zijn uitgeschakeld." +smtpDisabled = "E-mailmeldingen vereisen dat SMTP is ingeschakeld in de instellingen." +notifyOnly = "Er wordt een e-mail verzonden zonder het wachtwoord, om de gebruiker te laten weten dat een beheerder het heeft gewijzigd." +submit = "Wachtwoord bijwerken" +success = "Wachtwoord succesvol bijgewerkt" +error = "Bijwerken van wachtwoord mislukt" + [workspace.people.emailInvite] tab = "E-mailuitnodiging" description = "Typ of plak hieronder e-mailadressen, gescheiden door komma's. Gebruikers ontvangen inloggegevens via e-mail." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Er is minstens één e-mailadres vereist" submit = "Uitnodigingen verzenden" success = "Gebruiker(s) succesvol uitgenodigd" -partialSuccess = "Sommige uitnodigingen zijn mislukt" +partialFailure = "Sommige uitnodigingen zijn mislukt" allFailed = "Uitnodigen van gebruikers is mislukt" error = "Uitnodigingen verzenden is mislukt" @@ -5709,7 +5864,7 @@ title = "Grafiek van endpointgebruik" [usage.table] title = "Gedetailleerde statistieken" -endpoint = "Endpoint" +endpoint = "Eindpunt" visits = "Bezoeken" percentage = "Percentage" noData = "Geen gegevens beschikbaar" @@ -5770,6 +5925,7 @@ subtitle = "Log in met je Stirling-account" [setup.selfhosted] title = "Inloggen bij server" subtitle = "Vul je servergegevens in" +link = "of maak verbinding met een zelfgehost account" [setup.server] title = "Verbinden met server" @@ -5788,6 +5944,14 @@ description = "Voer de volledige URL van je self-hosted Stirling PDF-server in" emptyUrl = "Voer een server-URL in" unreachable = "Kan geen verbinding maken met server" testFailed = "Verbindingstest mislukt" +configFetch = "Ophalen van serverconfiguratie mislukt. Controleer de URL en probeer het opnieuw." + +[setup.server.error.securityDisabled] +title = "Inloggen niet ingeschakeld" +body = "Op deze server is inloggen niet ingeschakeld. Om verbinding te maken met deze server moet u authenticatie inschakelen:" +step1 = "Stel DOCKER_ENABLE_SECURITY=true in in uw omgeving" +step2 = "Of stel security.enableLogin=true in in settings.yml" +step3 = "Start de server opnieuw" [setup.login] title = "Inloggen" @@ -5797,6 +5961,13 @@ submit = "Inloggen" signInWith = "Inloggen met" oauthPending = "Browser wordt geopend voor authenticatie..." orContinueWith = "Of ga verder met e-mail" +serverRequirement = "Let op: op de server moet inloggen zijn ingeschakeld." +showInstructions = "Hoe inschakelen?" +hideInstructions = "Instructies verbergen" +instructions = "Om inloggen op uw Stirling PDF-server in te schakelen:" +instructionsEnvVar = "Stel de omgevingsvariabele in:" +instructionsOrYml = "Of in settings.yml:" +instructionsRestart = "Start vervolgens uw server opnieuw zodat de wijzigingen van kracht worden." [setup.login.username] label = "Gebruikersnaam" @@ -5853,6 +6024,7 @@ earlyAccess = "Vroege toegang" reset = "Wijzigingen resetten" downloadJson = "JSON downloaden" generatePdf = "PDF genereren" +saveChanges = "Wijzigingen opslaan" [pdfTextEditor.options.autoScaleText] title = "Tekst automatisch schalen zodat deze in vakken past" @@ -5890,6 +6062,8 @@ alpha = "Deze alpha-viewer is nog in ontwikkeling—bepaalde lettertypen, kleure [pdfTextEditor.empty] title = "Geen document geladen" subtitle = "Laad een PDF- of JSON-bestand om tekst te bewerken." +dropzone = "Sleep hier een PDF- of JSON-bestand naartoe, of klik om te bladeren" +dropzoneWithFiles = "Selecteer een bestand op het tabblad Bestanden, of sleep hier een PDF- of JSON-bestand naartoe, of klik om te bladeren" [pdfTextEditor.welcomeBanner] title = "Welkom bij PDF-teksteditor (Early Access)" diff --git a/frontend/public/locales/no-NB/translation.toml b/frontend/public/locales/no-NB/translation.toml index 21b873f01..c60238acf 100644 --- a/frontend/public/locales/no-NB/translation.toml +++ b/frontend/public/locales/no-NB/translation.toml @@ -131,7 +131,7 @@ unsupported = "Ikke støttet" [toolPanel] placeholder = "Velg et verktøy for å komme i gang" -alpha = "Alpha" +alpha = "Alfa" premiumFeature = "Premium-funksjon:" comingSoon = "Kommer snart:" @@ -163,6 +163,11 @@ unfavorite = "Fjern fra favoritter" fullscreen = "Bytt til fullskjerm-modus" sidebar = "Bytt til sidepanel-modus" +[backendStartup] +notFoundTitle = "Backend ikke funnet" +retry = "Prøv igjen" +unreachable = "Programmet kan for øyeblikket ikke koble til backend. Kontroller backend-status og nettverkstilkobling, og prøv igjen." + [zipWarning] title = "Stor ZIP-fil" message = "Denne ZIP-en inneholder {{count}} filer. Pakk ut likevel?" @@ -912,6 +917,9 @@ desc = "Bygg flertrinns arbeidsflyter ved å lenke sammen PDF-handlinger. Ideelt desc = "Legger PDF-er over hverandre" title = "Overlay PDF-er" +[home.pdfTextEditor] +title = "PDF-teksteditor" +desc = "Rediger eksisterende tekst og bilder i PDF-filer" [home.addText] tags = "tekst,merknad,etikett" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Tegnet signatur" defaultImageLabel = "Opplastet signatur" defaultTextLabel = "Tekstsignatur" saveButton = "Lagre signatur" +savePersonal = "Lagre personlig" +saveShared = "Lagre delt" saveUnavailable = "Opprett en signatur først for å lagre den." noChanges = "Gjeldende signatur er allerede lagret." +tempStorageTitle = "Midlertidig nettleserlagring" +tempStorageDescription = "Signaturer lagres bare i nettleseren din. De går tapt hvis du sletter nettleserdata eller bytter nettleser." +personalHeading = "Personlige signaturer" +sharedHeading = "Delte signaturer" +personalDescription = "Bare du kan se disse signaturene." +sharedDescription = "Alle brukere kan se og bruke disse signaturene." [sign.saved.type] canvas = "Tegning" @@ -3020,6 +3036,91 @@ title = "Få Info om PDF" header = "Få Info om PDF" submit = "Få Info" downloadJson = "Last ned JSON" +processing = "Henter ut informasjon..." +results = "Resultater" +noResults = "Kjør verktøyet for å generere en rapport." +downloads = "Nedlastinger" +noneDetected = "Ingen funnet" +indexTitle = "Indeks" + +[getPdfInfo.report] +entryLabel = "Fullstendig informasjonsoppsummering" +shortTitle = "PDF-informasjon" + +[getPdfInfo.sections] +metadata = "Metadata" +formFields = "Skjemafelt" +basicInfo = "Grunnleggende info" +documentInfo = "Dokumentinformasjon" +compliance = "Samsvar" +encryption = "Kryptering" +permissions = "Tillatelser" +other = "Annet" +perPageInfo = "Informasjon per side" +tableOfContents = "Innholdsfortegnelse" + +[getPdfInfo.other] +attachments = "Vedlegg" +embeddedFiles = "Innebygde filer" +javaScript = "JavaScript" +layers = "Lag" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Størrelse" +annotations = "Merknader" +images = "Bilder" +links = "Lenker" +fonts = "Skrifter" +xobjects = "XObject-antall" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Sider" +fileSize = "Filstørrelse" +pdfVersion = "PDF-versjon" +language = "Språk" +title = "PDF-sammendrag" +author = "Forfatter" +created = "Opprettet" +modified = "Endret" +permsAll = "Alle tillatelser tillatt" +permsRestricted = "{{count}} begrensninger" +permsMixed = "Noen tillatelser begrenset" +hasCompliance = "Har samsvarsstandarder" +noCompliance = "Ingen samsvarsstandarder" +basic = "Grunnleggende informasjon" +documentInfo = "Dokumentinformasjon" +securityTitle = "Sikkerhetsstatus" +technical = "Teknisk" +overviewTitle = "PDF-oversikt" + +[getPdfInfo.summary.security] +encrypted = "Kryptert PDF - passordbeskyttelse er aktiv" +unencrypted = "Ukryptert PDF - ingen passordbeskyttelse" + +[getPdfInfo.summary.tech] +images = "Bilder" +fonts = "Skrifter" +formFields = "Skjemafelt" +embeddedFiles = "Innebygde filer" +javaScript = "JavaScript" +layers = "Lag" +bookmarks = "Bokmerker" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "et navnløst dokument" +unknown = "Ukjent forfatter" +text = "Dette er en PDF på {{pages}} sider med tittelen {{title}}, opprettet av {{author}} (PDF-versjon {{version}})." + +[getPdfInfo.error] +partial = "Noen filer kunne ikke behandles." +unexpected = "Uventet feil under uttrekk." + +[getPdfInfo.status] +complete = "Uttrekk fullført" [extractPage] tags = "ekstrahere" @@ -3438,6 +3539,9 @@ signinTitle = "Vennligst logg inn" ssoSignIn = "Logg inn via Enkel Pålogging" oAuth2AutoCreateDisabled = "OAUTH2 Auto-Opretting av bruker deaktivert" oAuth2AdminBlockedUser = "Registrering eller pålogging for ikke-registrerte brukere er for øyeblikket blokkert. Vennligst kontakt administrator" +oAuth2RequiresLicense = "OAuth/SSO-pålogging krever en betalt lisens (Server eller Enterprise). Kontakt administratoren for å oppgradere planen din." +saml2RequiresLicense = "SAML-pålogging krever en betalt lisens (Server eller Enterprise). Kontakt administratoren for å oppgradere planen din." +maxUsersReached = "Maksimalt antall brukere er nådd for din nåværende lisens. Kontakt administratoren for å oppgradere planen din eller legge til flere brukerplasser." oauth2RequestNotFound = "Autentiseringsforespørsel ikke funnet" oauth2InvalidUserInfoResponse = "Ugyldig brukerinforespons" oauth2invalidRequest = "Ugyldig forespørsel" @@ -3846,14 +3950,17 @@ fitToWidth = "Tilpass til bredde" actualSize = "Faktisk størrelse" [viewer] +cannotPreviewFile = "Kan ikke forhåndsvise fil" +dualPageView = "Dobbelsidevisning" firstPage = "Første side" lastPage = "Siste side" -previousPage = "Forrige side" nextPage = "Neste side" +onlyPdfSupported = "Visningsprogrammet støtter bare PDF-filer. Denne filen ser ut til å ha et annet format." +previousPage = "Forrige side" +singlePageView = "Enkeltsidevisning" +unknownFile = "Ukjent fil" zoomIn = "Zoom inn" zoomOut = "Zoom ut" -singlePageView = "Enkeltsidevisning" -dualPageView = "Dobbelsidevisning" [rightRail] closeSelected = "Lukk valgte filer" @@ -3877,6 +3984,7 @@ toggleSidebar = "Vis/skjul sidepanel" exportSelected = "Eksporter valgte sider" toggleAnnotations = "Vis/skjul merknader" annotationMode = "Veksle merknadsmodus" +print = "Skriv ut PDF" draw = "Tegn" save = "Lagre" saveChanges = "Lagre endringer" @@ -4153,7 +4261,7 @@ description = "Spor brukerhandlinger og systemhendelser for etterlevelse og sikk [admin.settings.security.audit.level] label = "Revisjonsnivå" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=AV, 1=GRUNNLEGGENDE, 2=STANDARD, 3=DETALJERT" [admin.settings.security.audit.retentionDays] label = "Bevaring av revisjon (dager)" @@ -4494,6 +4602,7 @@ description = "URL eller filnavn til impressum (påkrevd i noen jurisdiksjoner)" title = "Premium og Enterprise" description = "Konfigurer din premium- eller enterprise-lisensnøkkel." license = "Lisenskonfigurasjon" +noInput = "Oppgi en lisensnøkkel eller fil" [admin.settings.premium.licenseKey] toggle = "Har du en lisensnøkkel eller sertifikatfil?" @@ -4511,6 +4620,25 @@ line1 = "Å overskrive gjeldende lisensnøkkel kan ikke angres." line2 = "Den forrige lisensen vil gå tapt permanent med mindre du har sikkerhetskopiert den et annet sted." line3 = "Viktig: Hold lisensnøkler private og sikre. Del dem aldri offentlig." +[admin.settings.premium.inputMethod] +text = "Lisensnøkkel" +file = "Sertifikatfil" + +[admin.settings.premium.file] +label = "Lisenssertifikatfil" +description = "Last opp .lic- eller .cert-lisensfilen din fra offline-kjøp" +choose = "Velg lisensfil" +selected = "Valgt: {{filename}} ({{size}})" +successMessage = "Lisensfilen ble lastet opp og aktivert. Omstart er ikke nødvendig." + +[admin.settings.premium.currentLicense] +title = "Aktiv lisens" +file = "Kilde: Lisensfil ({{path}})" +key = "Kilde: Lisensnøkkel" +type = "Type: {{type}}" +noInput = "Oppgi en lisensnøkkel eller last opp en sertifikatfil" +success = "Vellykket" + [admin.settings.premium.enabled] label = "Aktiver premiumfunksjoner" description = "Aktiver lisensnøkkelkontroller for pro-/enterprise-funksjoner" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} valgt" download = "Last ned" delete = "Slett" unsupported = "Ikke støttet" +active = "Aktiv" addToUpload = "Legg til i opplasting" +closeFile = "Lukk fil" deleteAll = "Slett alt" loadingFiles = "Laster filer..." noFiles = "Ingen filer tilgjengelig" @@ -5059,7 +5189,7 @@ title = "Erstatt-Inverter-Farge" [replace-color.options] fill = "Fyllfarge" -gradient = "Gradient" +gradient = "Fargeovergang" [replace-color.selectText] 1 = "Erstatt eller Inverter farge alternativer" @@ -5132,7 +5262,7 @@ upgrade = "Oppgrader nå →" freeTitle = "Serverlisens" overLimitTitle = "Serverlisens kreves" overLimitBody = "Lisensieringen vår tillater opptil {{freeTierLimit}} brukere gratis per server. Du har {{overLimitUserCopy}} Stirling-brukere. For å fortsette uten avbrudd, oppgrader til Stirling Server-planen – ubegrensede plasser, PDF-tekstredigering og full admin-kontroll for $99/server/mnd." -freeBody = "Vår Open-Core-lisensiering tillater opptil {{freeTierLimit}} brukere gratis per server. For å skalere uten avbrudd og få tidlig tilgang til vårt nye PDF-tekstredigeringsverktøy, anbefaler vi Stirling Server-planen – full redigering og ubegrensede plasser for $99/server/mnd." +freeBody = "Vår Open-Core-lisensiering tillater opptil {{freeTierLimit}} brukere gratis per server. For å skalere uten avbrudd anbefaler vi Stirling Server-planen - ubegrensede plasser og SSO-støtte for $99/server/mnd." [onboarding.desktopInstall] title = "Last ned" @@ -5237,6 +5367,31 @@ error = "Kunne ikke oppdatere brukerstatus" success = "Bruker slettet" error = "Kunne ikke slette bruker" +[workspace.people.changePassword] +action = "Endre passord" +title = "Endre passord" +subtitle = "Oppdater passordet for" +newPassword = "Nytt passord" +confirmPassword = "Bekreft passord" +placeholder = "Angi et nytt passord" +confirmPlaceholder = "Skriv inn det nye passordet på nytt" +passwordRequired = "Angi et nytt passord" +passwordMismatch = "Passordene samsvarer ikke" +generateRandom = "Generer sikkert passord" +generatedPreview = "Generert passord:" +copyTooltip = "Kopier til utklippstavle" +copiedToClipboard = "Passord kopiert til utklippstavle" +copyFailed = "Kunne ikke kopiere passord" +sendEmail = "Send e-post til brukeren om denne endringen" +includePassword = "Inkluder det nye passordet i e-posten" +forcePasswordChange = "Tving brukeren til å endre passord ved neste innlogging" +emailUnavailable = "Denne brukerens e-post er ikke en gyldig e-postadresse. Varsler er deaktivert." +smtpDisabled = "E-postvarsler krever at SMTP er aktivert i innstillingene." +notifyOnly = "Det sendes en e-post uten passordet som informerer brukeren om at en admin har endret det." +submit = "Oppdater passord" +success = "Passord oppdatert" +error = "Kunne ikke oppdatere passord" + [workspace.people.emailInvite] tab = "E-postinvitasjon" description = "Skriv eller lim inn e-poster nedenfor, separert med komma. Brukere vil motta innloggingsdetaljer via e-post." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Minst én e-postadresse er påkrevd" submit = "Send invitasjoner" success = "bruker(e) invitert" -partialSuccess = "Noen invitasjoner mislyktes" +partialFailure = "Noen invitasjoner mislyktes" allFailed = "Kunne ikke invitere brukere" error = "Kunne ikke sende invitasjoner" @@ -5770,6 +5925,7 @@ subtitle = "Logg inn med Stirling-kontoen din" [setup.selfhosted] title = "Logg inn på server" subtitle = "Oppgi serverlegitimasjonen din" +link = "eller koble til en selvhostet konto" [setup.server] title = "Koble til server" @@ -5788,6 +5944,14 @@ description = "Skriv inn full URL til din selvhostede Stirling PDF-server" emptyUrl = "Skriv inn en server-URL" unreachable = "Kunne ikke koble til server" testFailed = "Tilkoblingstest mislyktes" +configFetch = "Kunne ikke hente serverkonfigurasjon. Kontroller URL-en og prøv igjen." + +[setup.server.error.securityDisabled] +title = "Innlogging ikke aktivert" +body = "Denne serveren har ikke innlogging aktivert. For å koble til denne serveren må du aktivere autentisering:" +step1 = "Angi DOCKER_ENABLE_SECURITY=true i miljøet ditt" +step2 = "Eller angi security.enableLogin=true i settings.yml" +step3 = "Start serveren på nytt" [setup.login] title = "Logg inn" @@ -5797,6 +5961,13 @@ submit = "Logg inn" signInWith = "Logg inn med" oauthPending = "Åpner nettleser for autentisering..." orContinueWith = "Eller fortsett med e-post" +serverRequirement = "Merk: Serveren må ha pålogging aktivert." +showInstructions = "Hvordan aktivere?" +hideInstructions = "Skjul instruksjoner" +instructions = "Slik aktiverer du pålogging på din Stirling PDF-server:" +instructionsEnvVar = "Sett miljøvariabelen:" +instructionsOrYml = "Eller i settings.yml:" +instructionsRestart = "Start deretter serveren på nytt for at endringene skal tre i kraft." [setup.login.username] label = "Brukernavn" @@ -5840,7 +6011,7 @@ paragraph = "Avsnittsside" sparse = "Sparsom tekst" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automatisk" paragraph = "Avsnitt" singleLine = "Én linje" @@ -5853,6 +6024,7 @@ earlyAccess = "Tidlig tilgang" reset = "Tilbakestill endringer" downloadJson = "Last ned JSON" generatePdf = "Generer PDF" +saveChanges = "Lagre endringer" [pdfTextEditor.options.autoScaleText] title = "Autoskalere tekst til å passe i bokser" @@ -5890,6 +6062,8 @@ alpha = "Denne alfa-visningen er fortsatt under utvikling—visse skrifttyper, f [pdfTextEditor.empty] title = "Ingen dokument lastet inn" subtitle = "Last inn en PDF- eller JSON-fil for å begynne å redigere tekstinnhold." +dropzone = "Dra og slipp en PDF- eller JSON-fil her, eller klikk for å bla gjennom" +dropzoneWithFiles = "Velg en fil fra fanen Filer, eller dra og slipp en PDF- eller JSON-fil her, eller klikk for å bla gjennom" [pdfTextEditor.welcomeBanner] title = "Velkommen til PDF Text Editor (Tidlig tilgang)" diff --git a/frontend/public/locales/pl-PL/translation.toml b/frontend/public/locales/pl-PL/translation.toml index bbde87022..c066d1384 100644 --- a/frontend/public/locales/pl-PL/translation.toml +++ b/frontend/public/locales/pl-PL/translation.toml @@ -131,7 +131,7 @@ unsupported = "Nieobsługiwane" [toolPanel] placeholder = "Wybierz narzędzie, aby zacząć" -alpha = "Alpha" +alpha = "Alfa" premiumFeature = "Funkcja premium:" comingSoon = "Wkrótce:" @@ -163,6 +163,11 @@ unfavorite = "Usuń z ulubionych" fullscreen = "Przełącz na tryb pełnoekranowy" sidebar = "Przełącz na tryb paska bocznego" +[backendStartup] +notFoundTitle = "Nie znaleziono backendu" +retry = "Spróbuj ponownie" +unreachable = "Aplikacja nie może obecnie połączyć się z backendem. Sprawdź stan backendu i łączność sieciową, a następnie spróbuj ponownie." + [zipWarning] title = "Duży plik ZIP" message = "Ten ZIP zawiera {{count}} plików. Mimo to rozpakować?" @@ -563,7 +568,7 @@ loading = "Ładowanie..." failedToLoad = "Nie udało się załadować danych punktów końcowych. Spróbuj odświeżyć." home = "Strona główna" login = "Logowanie" -top = "Top" +top = "Najlepsze" numberOfVisits = "Liczba wizyt" visitsTooltip = "Wizyty: {0} ({1}% całości)" retry = "Spróbuj ponownie" @@ -912,6 +917,9 @@ desc = "Buduj wieloetapowe przepływy, łącząc akcje PDF. Idealne do powtarzaj desc = "Nakłada dokumenty PDF na siebie" title = "Nałóż PDFa" +[home.pdfTextEditor] +title = "Edytor tekstu PDF" +desc = "Edytuj istniejący tekst i obrazy w plikach PDF" [home.addText] tags = "tekst,adnotacja,etykieta" @@ -1217,7 +1225,7 @@ odtExt = "Tekst OpenDocument (.odt)" pptExt = "PowerPoint (.pptx)" odpExt = "Prezentacja OpenDocument (.odp)" txtExt = "Tekst niesformatowany (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Format RTF (.rtf)" selectedFiles = "Wybrane pliki" noFileSelected = "Nie wybrano pliku. Użyj panelu plików, aby dodać pliki." convertFiles = "Konwertuj pliki" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Podpis rysowany" defaultImageLabel = "Przesłany podpis" defaultTextLabel = "Podpis wpisany" saveButton = "Zapisz podpis" +savePersonal = "Zapisz osobiste" +saveShared = "Zapisz udostępnione" saveUnavailable = "Najpierw utwórz podpis, aby go zapisać." noChanges = "Bieżący podpis jest już zapisany." +tempStorageTitle = "Tymczasowe przechowywanie w przeglądarce" +tempStorageDescription = "Podpisy są przechowywane tylko w Twojej przeglądarce. Zostaną utracone po wyczyszczeniu danych przeglądarki lub zmianie przeglądarki." +personalHeading = "Osobiste podpisy" +sharedHeading = "Udostępnione podpisy" +personalDescription = "Tylko Ty widzisz te podpisy." +sharedDescription = "Wszyscy użytkownicy mogą widzieć i używać tych podpisów." [sign.saved.type] canvas = "Rysunek" @@ -3020,6 +3036,91 @@ title = "Pobierz informacje o pliku PDF" header = "Pobierz informacje o pliku PDF" submit = "Pobierz informacje" downloadJson = "Pobierz JSON z zawartością" +processing = "Wyodrębnianie informacji..." +results = "Wyniki" +noResults = "Uruchom narzędzie, aby wygenerować raport." +downloads = "Pobrania" +noneDetected = "Nic nie wykryto" +indexTitle = "Indeks" + +[getPdfInfo.report] +entryLabel = "Pełne podsumowanie informacji" +shortTitle = "Informacje o PDF" + +[getPdfInfo.sections] +metadata = "Metadane" +formFields = "Pola formularza" +basicInfo = "Informacje podstawowe" +documentInfo = "Informacje o dokumencie" +compliance = "Zgodność" +encryption = "Szyfrowanie" +permissions = "Uprawnienia" +other = "Inne" +perPageInfo = "Informacje dla każdej strony" +tableOfContents = "Spis treści" + +[getPdfInfo.other] +attachments = "Załączniki" +embeddedFiles = "Osadzone pliki" +javaScript = "JavaScript" +layers = "Warstwy" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Rozmiar" +annotations = "Adnotacje" +images = "Obrazy" +links = "Linki" +fonts = "Czcionki" +xobjects = "Liczba XObject" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Strony" +fileSize = "Rozmiar pliku" +pdfVersion = "Wersja PDF" +language = "Język" +title = "Podsumowanie PDF" +author = "Autor" +created = "Utworzono" +modified = "Zmodyfikowano" +permsAll = "Wszystkie uprawnienia dozwolone" +permsRestricted = "{{count}} ograniczeń" +permsMixed = "Niektóre uprawnienia ograniczone" +hasCompliance = "Spełnia standardy zgodności" +noCompliance = "Brak standardów zgodności" +basic = "Informacje podstawowe" +documentInfo = "Informacje o dokumencie" +securityTitle = "Stan zabezpieczeń" +technical = "Techniczne" +overviewTitle = "Przegląd PDF" + +[getPdfInfo.summary.security] +encrypted = "Zaszyfrowany PDF - obecne zabezpieczenie hasłem" +unencrypted = "Niezaszyfrowany PDF - brak zabezpieczenia hasłem" + +[getPdfInfo.summary.tech] +images = "Obrazy" +fonts = "Czcionki" +formFields = "Pola formularza" +embeddedFiles = "Osadzone pliki" +javaScript = "JavaScript" +layers = "Warstwy" +bookmarks = "Zakładki" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "niezatytułowany dokument" +unknown = "Nieznany autor" +text = "To jest {{pages}}-stronicowy plik PDF zatytułowany {{title}}, utworzony przez {{author}} (wersja PDF {{version}})." + +[getPdfInfo.error] +partial = "Niektórych plików nie udało się przetworzyć." +unexpected = "Nieoczekiwany błąd podczas wyodrębniania." + +[getPdfInfo.status] +complete = "Zakończono wyodrębnianie" [extractPage] tags = "wydobycie,separacja,wyciaganie" @@ -3438,6 +3539,9 @@ signinTitle = "Zaloguj się" ssoSignIn = "Zaloguj się za pomocą logowania jednokrotnego" oAuth2AutoCreateDisabled = "Wyłączono automatyczne tworzenie użytkownika OAUTH2" oAuth2AdminBlockedUser = "Rejestracja lub logowanie niezarejestrowanych użytkowników jest obecnie zablokowane. Prosimy o kontakt z administratorem." +oAuth2RequiresLicense = "Logowanie OAuth/SSO wymaga płatnej licencji (Server lub Enterprise). Skontaktuj się z administratorem, aby uaktualnić swój plan." +saml2RequiresLicense = "Logowanie SAML wymaga płatnej licencji (Server lub Enterprise). Skontaktuj się z administratorem, aby uaktualnić swój plan." +maxUsersReached = "Osiągnięto maksymalną liczbę użytkowników dla Twojej obecnej licencji. Skontaktuj się z administratorem, aby uaktualnić plan lub dodać więcej miejsc." oauth2RequestNotFound = "Błąd logowania OAuth2" oauth2InvalidUserInfoResponse = "Niewłaściwe dane logowania" oauth2invalidRequest = "Nieprawidłowe żądanie" @@ -3533,7 +3637,7 @@ title = "PDF do pojedyńczej strony" header = "PDF do pojedyńczej strony" submit = "Zapisz dokument jako PDF z jedną stroną" description = "To narzędzie scali wszystkie strony Twojego PDF w jedną dużą stronę. Szerokość pozostanie taka jak w oryginalnych stronach, a wysokość będzie sumą wysokości wszystkich stron." -filenamePrefix = "single_page" +filenamePrefix = "pojedyncza_strona" [pdfToSinglePage.files] placeholder = "Wybierz plik PDF w widoku głównym, aby rozpocząć" @@ -3846,14 +3950,17 @@ fitToWidth = "Dopasuj do szerokości" actualSize = "Rzeczywisty rozmiar" [viewer] +cannotPreviewFile = "Nie można wyświetlić podglądu pliku" +dualPageView = "Widok dwóch stron" firstPage = "Pierwsza strona" lastPage = "Ostatnia strona" -previousPage = "Poprzednia strona" nextPage = "Następna strona" +onlyPdfSupported = "Przeglądarka obsługuje tylko pliki PDF. Ten plik wydaje się mieć inny format." +previousPage = "Poprzednia strona" +singlePageView = "Widok pojedynczej strony" +unknownFile = "Nieznany plik" zoomIn = "Powiększ" zoomOut = "Pomniejsz" -singlePageView = "Widok pojedynczej strony" -dualPageView = "Widok dwóch stron" [rightRail] closeSelected = "Zamknij wybrane pliki" @@ -3877,6 +3984,7 @@ toggleSidebar = "Przełącz panel boczny" exportSelected = "Eksportuj wybrane strony" toggleAnnotations = "Przełącz widoczność adnotacji" annotationMode = "Przełącz tryb adnotacji" +print = "Drukuj PDF" draw = "Rysuj" save = "Zapisz" saveChanges = "Zapisz zmiany" @@ -4153,7 +4261,7 @@ description = "Śledź działania użytkowników i zdarzenia systemowe na potrze [admin.settings.security.audit.level] label = "Poziom audytu" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=WYŁ., 1=PODSTAWOWY, 2=STANDARDOWY, 3=SZCZEGÓŁOWY" [admin.settings.security.audit.retentionDays] label = "Przechowywanie audytu (dni)" @@ -4494,6 +4602,7 @@ description = "URL lub nazwa pliku do impressum (wymagane w niektórych jurysdyk title = "Premium i Enterprise" description = "Skonfiguruj swój klucz licencyjny premium lub enterprise." license = "Konfiguracja licencji" +noInput = "Podaj klucz licencyjny lub plik" [admin.settings.premium.licenseKey] toggle = "Masz klucz licencyjny lub plik certyfikatu?" @@ -4511,6 +4620,25 @@ line1 = "Nadpisania bieżącego klucza licencyjnego nie można cofnąć." line2 = "Poprzednia licencja zostanie trwale utracona, jeśli nie masz jej kopii zapasowej." line3 = "Ważne: przechowuj klucze licencyjne prywatnie i bezpiecznie. Nigdy nie udostępniaj ich publicznie." +[admin.settings.premium.inputMethod] +text = "Klucz licencyjny" +file = "Plik certyfikatu" + +[admin.settings.premium.file] +label = "Plik certyfikatu licencji" +description = "Prześlij swój plik licencji .lic lub .cert z zakupów offline" +choose = "Wybierz plik licencji" +selected = "Wybrano: {{filename}} ({{size}})" +successMessage = "Plik licencji przesłano i pomyślnie aktywowano. Ponowne uruchomienie nie jest wymagane." + +[admin.settings.premium.currentLicense] +title = "Aktywna licencja" +file = "Źródło: plik licencji ({{path}})" +key = "Źródło: klucz licencyjny" +type = "Typ: {{type}}" +noInput = "Podaj klucz licencyjny lub prześlij plik certyfikatu" +success = "Sukces" + [admin.settings.premium.enabled] label = "Włącz funkcje premium" description = "Włącz weryfikację klucza licencyjnego dla funkcji pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} wybrane" download = "Pobierz" delete = "usuń" unsupported = "Nieobsługiwane" +active = "Aktywny" addToUpload = "Dodaj do przesyłania" +closeFile = "Zamknij plik" deleteAll = "Usuń wszystko" loadingFiles = "Ładowanie plików..." noFiles = "Brak dostępnych plików" @@ -5132,7 +5262,7 @@ upgrade = "Ulepsz teraz →" freeTitle = "Licencja serwera" overLimitTitle = "Wymagana licencja serwera" overLimitBody = "Nasza licencja pozwala na maks. {{freeTierLimit}} użytkowników bez opłat na serwer. Masz {{overLimitUserCopy}} użytkowników Stirling. Aby kontynuować bez przerw, przejdź na plan Stirling Server – nielimitowane miejsca, edycja tekstu PDF i pełna kontrola administracyjna za 99 USD/serwer/mies." -freeBody = "Nasza licencja Open-Core pozwala na maks. {{freeTierLimit}} użytkowników bez opłat na serwer. Aby skalować bez przerw i uzyskać wczesny dostęp do nowego narzędzia edycji tekstu PDF, polecamy plan Stirling Server – pełna edycja i nielimitowane miejsca za 99 USD/serwer/mies." +freeBody = "Nasza licencja Open-Core pozwala na maksymalnie {{freeTierLimit}} użytkowników bezpłatnie na serwer. Aby skalować bez zakłóceń, zalecamy plan Stirling Server - nielimitowana liczba miejsc i obsługa SSO za $99/serwer/mies." [onboarding.desktopInstall] title = "Pobierz" @@ -5237,6 +5367,31 @@ error = "Nie udało się zaktualizować statusu użytkownika" success = "Użytkownik usunięty pomyślnie" error = "Nie udało się usunąć użytkownika" +[workspace.people.changePassword] +action = "Zmień hasło" +title = "Zmień hasło" +subtitle = "Zaktualizuj hasło dla" +newPassword = "Nowe hasło" +confirmPassword = "Potwierdź hasło" +placeholder = "Wprowadź nowe hasło" +confirmPlaceholder = "Wprowadź ponownie nowe hasło" +passwordRequired = "Wprowadź nowe hasło" +passwordMismatch = "Hasła nie są zgodne" +generateRandom = "Wygeneruj bezpieczne hasło" +generatedPreview = "Wygenerowane hasło:" +copyTooltip = "Kopiuj do schowka" +copiedToClipboard = "Hasło skopiowano do schowka" +copyFailed = "Nie udało się skopiować hasła" +sendEmail = "Wyślij użytkownikowi e-mail o tej zmianie" +includePassword = "Dołącz nowe hasło do e-maila" +forcePasswordChange = "Wymuś zmianę hasła przy następnym logowaniu" +emailUnavailable = "E-mail tego użytkownika nie jest prawidłowym adresem. Powiadomienia są wyłączone." +smtpDisabled = "Powiadomienia e-mail wymagają włączenia SMTP w ustawieniach." +notifyOnly = "Zostanie wysłany e-mail bez hasła, informujący użytkownika, że administrator je zmienił." +submit = "Zaktualizuj hasło" +success = "Hasło zaktualizowano pomyślnie" +error = "Nie udało się zaktualizować hasła" + [workspace.people.emailInvite] tab = "Zaproszenie e‑mail" description = "Wpisz lub wklej e‑maile poniżej, rozdzielone przecinkami. Użytkownicy otrzymają dane logowania e‑mailem." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Wymagany jest co najmniej jeden adres e‑mail" submit = "Wyślij zaproszenia" success = "Pomyślnie zaproszono użytkowników" -partialSuccess = "Niektóre zaproszenia nie powiodły się" +partialFailure = "Niektóre zaproszenia nie powiodły się" allFailed = "Nie udało się zaprosić użytkowników" error = "Nie udało się wysłać zaproszeń" @@ -5541,7 +5696,7 @@ emailInvalid = "Wpisz poprawny adres e‑mail" title = "Podaj e‑mail" description = "Użyjemy go do wysłania klucza licencyjnego i rachunków." emailLabel = "Adres e‑mail" -emailPlaceholder = "your@email.com" +emailPlaceholder = "twoj@email.com" continue = "Kontynuuj" modalTitle = "Zaczynamy – {{planName}}" @@ -5770,6 +5925,7 @@ subtitle = "Zaloguj się na konto Stirling" [setup.selfhosted] title = "Zaloguj się do serwera" subtitle = "Wprowadź dane logowania do serwera" +link = "lub połącz się z kontem hostowanym samodzielnie" [setup.server] title = "Połącz z serwerem" @@ -5788,6 +5944,14 @@ description = "Wpisz pełny URL własnego serwera Stirling PDF" emptyUrl = "Wpisz URL serwera" unreachable = "Nie można połączyć z serwerem" testFailed = "Test połączenia nie powiódł się" +configFetch = "Nie udało się pobrać konfiguracji serwera. Sprawdź URL i spróbuj ponownie." + +[setup.server.error.securityDisabled] +title = "Logowanie nie jest włączone" +body = "Na tym serwerze logowanie nie jest włączone. Aby się połączyć, musisz włączyć uwierzytelnianie:" +step1 = "Ustaw w środowisku DOCKER_ENABLE_SECURITY=true" +step2 = "Lub ustaw security.enableLogin=true w pliku settings.yml" +step3 = "Uruchom ponownie serwer" [setup.login] title = "Zaloguj się" @@ -5797,6 +5961,13 @@ submit = "Zaloguj" signInWith = "Zaloguj przez" oauthPending = "Otwieranie przeglądarki do uwierzytelnienia..." orContinueWith = "Lub kontynuuj e‑mailem" +serverRequirement = "Uwaga: Na serwerze musi być włączone logowanie." +showInstructions = "Jak włączyć?" +hideInstructions = "Ukryj instrukcje" +instructions = "Aby włączyć logowanie na swoim serwerze Stirling PDF:" +instructionsEnvVar = "Ustaw zmienną środowiskową:" +instructionsOrYml = "Lub w settings.yml:" +instructionsRestart = "Następnie uruchom ponownie serwer, aby zmiany zaczęły obowiązywać." [setup.login.username] label = "Nazwa użytkownika" @@ -5840,7 +6011,7 @@ paragraph = "Strona akapitowa" sparse = "Rzadki tekst" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automatycznie" paragraph = "Akapit" singleLine = "Pojedyncza linia" @@ -5853,6 +6024,7 @@ earlyAccess = "Wczesny dostęp" reset = "Resetuj zmiany" downloadJson = "Pobierz JSON" generatePdf = "Generuj PDF" +saveChanges = "Zapisz zmiany" [pdfTextEditor.options.autoScaleText] title = "Automatycznie skaluj tekst do pól" @@ -5890,6 +6062,8 @@ alpha = "Ten podgląd alfa jest wciąż rozwijany — niektóre czcionki, kolory [pdfTextEditor.empty] title = "Nie wczytano dokumentu" subtitle = "Wczytaj plik PDF lub JSON, aby rozpocząć edycję treści tekstowych." +dropzone = "Przeciągnij i upuść tutaj plik PDF lub JSON albo kliknij, aby przeglądać" +dropzoneWithFiles = "Wybierz plik z karty Pliki, przeciągnij i upuść tutaj plik PDF lub JSON albo kliknij, aby przeglądać" [pdfTextEditor.welcomeBanner] title = "Witamy w edytorze tekstu PDF (wczesny dostęp)" diff --git a/frontend/public/locales/pt-BR/translation.toml b/frontend/public/locales/pt-BR/translation.toml index 76dff8f2d..f146113a2 100644 --- a/frontend/public/locales/pt-BR/translation.toml +++ b/frontend/public/locales/pt-BR/translation.toml @@ -131,7 +131,7 @@ unsupported = "Não suportado" [toolPanel] placeholder = "Escolha uma ferramenta para começar" -alpha = "Alpha" +alpha = "Alfa" premiumFeature = "Recurso premium:" comingSoon = "Em breve:" @@ -163,6 +163,11 @@ unfavorite = "Remover dos favoritos" fullscreen = "Alternar para modo tela cheia" sidebar = "Alternar para modo barra lateral" +[backendStartup] +notFoundTitle = "Backend não encontrado" +retry = "Tentar novamente" +unreachable = "No momento, o aplicativo não consegue se conectar ao backend. Verifique o status do backend e a conectividade de rede e tente novamente." + [zipWarning] title = "Arquivo ZIP grande" message = "Este ZIP contém {{count}} arquivos. Extrair mesmo assim?" @@ -912,6 +917,9 @@ desc = "Crie fluxos de trabalho de várias etapas encadeando ações de PDF. Ide desc = "Sobrepor um PDF sobre outro" title = "Sobrepor PDFs" +[home.pdfTextEditor] +title = "Editor de Texto em PDF" +desc = "Edite texto e imagens existentes em PDFs" [home.addText] tags = "texto,anotação,rótulo" @@ -1217,7 +1225,7 @@ odtExt = "Texto OpenDocument (.odt)" pptExt = "PowerPoint (.pptx)" odpExt = "Apresentação OpenDocument (.odp)" txtExt = "Texto simples (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Formato Rich Text (.rtf)" selectedFiles = "Arquivos selecionados" noFileSelected = "Nenhum arquivo selecionado. Use o painel de arquivos para adicionar arquivos." convertFiles = "Converter arquivos" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Assinatura desenhada" defaultImageLabel = "Assinatura enviada" defaultTextLabel = "Assinatura digitada" saveButton = "Salvar assinatura" +savePersonal = "Salvar pessoal" +saveShared = "Salvar compartilhado" saveUnavailable = "Crie uma assinatura primeiro para salvá-la." noChanges = "A assinatura atual já está salva." +tempStorageTitle = "Armazenamento temporário do navegador" +tempStorageDescription = "As assinaturas são armazenadas apenas no seu navegador. Elas serão perdidas se você limpar os dados do navegador ou trocar de navegador." +personalHeading = "Assinaturas pessoais" +sharedHeading = "Assinaturas compartilhadas" +personalDescription = "Somente você pode ver essas assinaturas." +sharedDescription = "Todos os usuários podem ver e usar essas assinaturas." [sign.saved.type] canvas = "Desenho" @@ -3020,6 +3036,91 @@ title = "Obter Informações do PDF" header = "Obter Informações do PDF" submit = "Obter Informações" downloadJson = "Baixar JSON" +processing = "Extraindo informações..." +results = "Resultados" +noResults = "Execute a ferramenta para gerar um relatório." +downloads = "Downloads" +noneDetected = "Nenhum detectado" +indexTitle = "Índice" + +[getPdfInfo.report] +entryLabel = "Resumo completo das informações" +shortTitle = "Informações do PDF" + +[getPdfInfo.sections] +metadata = "Metadados" +formFields = "Campos de formulário" +basicInfo = "Informações básicas" +documentInfo = "Informações do documento" +compliance = "Conformidade" +encryption = "Criptografia" +permissions = "Permissões" +other = "Outros" +perPageInfo = "Informações por página" +tableOfContents = "Sumário" + +[getPdfInfo.other] +attachments = "Anexos" +embeddedFiles = "Arquivos incorporados" +javaScript = "JavaScript" +layers = "Camadas" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Tamanho" +annotations = "Anotações" +images = "Imagens" +links = "Links" +fonts = "Fontes" +xobjects = "Contagem de XObjects" +multimedia = "Multimídia" + +[getPdfInfo.summary] +pages = "Páginas" +fileSize = "Tamanho do arquivo" +pdfVersion = "Versão do PDF" +language = "Idioma" +title = "Resumo do PDF" +author = "Autor" +created = "Criado" +modified = "Modificado" +permsAll = "Todas as permissões permitidas" +permsRestricted = "{{count}} restrições" +permsMixed = "Algumas permissões restritas" +hasCompliance = "Possui padrões de conformidade" +noCompliance = "Sem padrões de conformidade" +basic = "Informações básicas" +documentInfo = "Informações do documento" +securityTitle = "Status de segurança" +technical = "Técnico" +overviewTitle = "Visão geral do PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF criptografado - Proteção por senha ativada" +unencrypted = "PDF não criptografado - Sem proteção por senha" + +[getPdfInfo.summary.tech] +images = "Imagens" +fonts = "Fontes" +formFields = "Campos de formulário" +embeddedFiles = "Arquivos incorporados" +javaScript = "JavaScript" +layers = "Camadas" +bookmarks = "Marcadores" +multimedia = "Multimídia" + +[getPdfInfo.summary.overview] +untitled = "um documento sem título" +unknown = "Autor desconhecido" +text = "Este é um PDF de {{pages}} páginas intitulado {{title}} criado por {{author}} (versão do PDF {{version}})." + +[getPdfInfo.error] +partial = "Alguns arquivos não puderam ser processados." +unexpected = "Erro inesperado durante a extração." + +[getPdfInfo.status] +complete = "Extração concluída" [extractPage] tags = "extrair" @@ -3438,6 +3539,9 @@ signinTitle = "Por favor, inicie a sessão" ssoSignIn = "Iniciar sessão através de login único (SSO)" oAuth2AutoCreateDisabled = "Auto-Criar Usuário OAUTH2 Desativado" oAuth2AdminBlockedUser = "O registro ou login de usuários não registrados está atualmente bloqueado. Entre em contato com o administrador." +oAuth2RequiresLicense = "O login via OAuth/SSO requer uma licença paga (Server ou Enterprise). Entre em contato com o administrador para atualizar seu plano." +saml2RequiresLicense = "O login via SAML requer uma licença paga (Server ou Enterprise). Entre em contato com o administrador para atualizar seu plano." +maxUsersReached = "Número máximo de usuários atingido para sua licença atual. Entre em contato com o administrador para atualizar seu plano ou adicionar mais assentos." oauth2RequestNotFound = "Solicitação de autorização não encontrada" oauth2InvalidUserInfoResponse = "Resposta de informação de usuário inválida" oauth2invalidRequest = "Requisição Inválida" @@ -3846,14 +3950,17 @@ fitToWidth = "Ajustar à largura" actualSize = "Tamanho real" [viewer] +cannotPreviewFile = "Não é possível visualizar o arquivo" +dualPageView = "Visualização de duas páginas" firstPage = "Primeira página" lastPage = "Última página" -previousPage = "Página anterior" nextPage = "Próxima página" +onlyPdfSupported = "O visualizador oferece suporte apenas a arquivos PDF. Este arquivo parece estar em um formato diferente." +previousPage = "Página anterior" +singlePageView = "Visualização de página única" +unknownFile = "Arquivo desconhecido" zoomIn = "Ampliar" zoomOut = "Reduzir" -singlePageView = "Visualização de página única" -dualPageView = "Visualização de duas páginas" [rightRail] closeSelected = "Fechar arquivos selecionados" @@ -3877,6 +3984,7 @@ toggleSidebar = "Alternar barra lateral" exportSelected = "Exportar páginas selecionadas" toggleAnnotations = "Alternar visibilidade das anotações" annotationMode = "Alternar modo de anotação" +print = "Imprimir PDF" draw = "Desenhar" save = "Salvar" saveChanges = "Salvar alterações" @@ -3925,7 +4033,7 @@ files = "Arquivos" activity = "Ativ." help = "Ajuda" account = "Conta" -config = "Config" +config = "Configurações" settings = "Ajustes" adminSettings = "Ajustes admin" allTools = "Ferram." @@ -4153,7 +4261,7 @@ description = "Rastrear ações do usuário e eventos do sistema para conformida [admin.settings.security.audit.level] label = "Nível de auditoria" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=DESLIGADO, 1=BÁSICO, 2=PADRÃO, 3=DETALHADO" [admin.settings.security.audit.retentionDays] label = "Retenção de auditoria (dias)" @@ -4235,11 +4343,11 @@ label = "URL do emissor" description = "A URL do emissor do provedor OAuth2" [admin.settings.connections.oauth2.clientId] -label = "Client ID" +label = "ID do cliente" description = "O Client ID do OAuth2 do seu provedor" [admin.settings.connections.oauth2.clientSecret] -label = "Client Secret" +label = "Segredo do cliente" description = "O Client Secret do OAuth2 do seu provedor" [admin.settings.connections.oauth2.useAsUsername] @@ -4494,6 +4602,7 @@ description = "URL ou nome de arquivo do impressum (exigido em algumas jurisdiç title = "Premium e Enterprise" description = "Configurar sua chave de licença premium ou enterprise." license = "Configuração de licença" +noInput = "Forneça uma chave ou arquivo de licença" [admin.settings.premium.licenseKey] toggle = "Tem uma chave de licença ou arquivo de certificado?" @@ -4511,6 +4620,25 @@ line1 = "Substituir sua chave de licença atual não pode ser desfeito." line2 = "Sua licença anterior será perdida permanentemente, a menos que você tenha um backup em outro lugar." line3 = "Importante: mantenha chaves de licença privadas e seguras. Nunca as compartilhe publicamente." +[admin.settings.premium.inputMethod] +text = "Chave de licença" +file = "Arquivo de certificado" + +[admin.settings.premium.file] +label = "Arquivo de certificado de licença" +description = "Faça upload do seu arquivo de licença .lic ou .cert de compras offline" +choose = "Escolher arquivo de licença" +selected = "Selecionado: {{filename}} ({{size}})" +successMessage = "Arquivo de licença enviado e ativado com sucesso. Não é necessário reiniciar." + +[admin.settings.premium.currentLicense] +title = "Licença ativa" +file = "Origem: arquivo de licença ({{path}})" +key = "Origem: chave de licença" +type = "Tipo: {{type}}" +noInput = "Forneça uma chave de licença ou envie um arquivo de certificado" +success = "Sucesso" + [admin.settings.premium.enabled] label = "Habilitar recursos Premium" description = "Habilitar verificação de chave de licença para recursos pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} selecionado(s)" download = "Baixar (JSON)" delete = "Apagar" unsupported = "Não suportado" +active = "Ativo" addToUpload = "Adicionar ao upload" +closeFile = "Fechar arquivo" deleteAll = "Excluir tudo" loadingFiles = "Carregando arquivos..." noFiles = "Nenhum arquivo disponível" @@ -5132,7 +5262,7 @@ upgrade = "Fazer upgrade agora →" freeTitle = "Licença do servidor" overLimitTitle = "Necessária licença do servidor" overLimitBody = "Nossa licença permite até {{freeTierLimit}} usuários grátis por servidor. Você tem {{overLimitUserCopy}} usuários do Stirling. Para continuar sem interrupções, faça upgrade para o plano Stirling Server - assentos ilimitados, edição de texto em PDF e controle total de admin por US$ 99/servidor/mês." -freeBody = "Nossa licença Open-Core permite até {{freeTierLimit}} usuários grátis por servidor. Para escalar sem interrupções e ter acesso antecipado à nova ferramenta de edição de texto em PDF, recomendamos o plano Stirling Server - edição completa e assentos ilimitados por US$ 99/servidor/mês." +freeBody = "Nossa licença Open-Core permite até {{freeTierLimit}} usuários gratuitos por servidor. Para escalar sem interrupções, recomendamos o plano Stirling Server - assentos ilimitados e suporte a SSO por US$ 99/servidor/mês." [onboarding.desktopInstall] title = "Download" @@ -5178,7 +5308,7 @@ active = "Ativo" disabled = "Desativado" activeSession = "Sessão ativa" member = "Membro" -admin = "Admin" +admin = "Administrador" editRole = "Editar função" enable = "Ativar" disable = "Desativar" @@ -5237,6 +5367,31 @@ error = "Falha ao atualizar o status do usuário" success = "Usuário excluído com sucesso" error = "Falha ao excluir usuário" +[workspace.people.changePassword] +action = "Alterar senha" +title = "Alterar senha" +subtitle = "Atualizar a senha de" +newPassword = "Nova senha" +confirmPassword = "Confirmar senha" +placeholder = "Insira uma nova senha" +confirmPlaceholder = "Digite novamente a nova senha" +passwordRequired = "Por favor, insira uma nova senha" +passwordMismatch = "As senhas não coincidem" +generateRandom = "Gerar senha segura" +generatedPreview = "Senha gerada:" +copyTooltip = "Copiar para a área de transferência" +copiedToClipboard = "Senha copiada para a área de transferência" +copyFailed = "Falha ao copiar a senha" +sendEmail = "Enviar email ao usuário sobre esta alteração" +includePassword = "Incluir a nova senha no email" +forcePasswordChange = "Exigir que o usuário altere a senha no próximo login" +emailUnavailable = "O email deste usuário não é um endereço válido. As notificações estão desativadas." +smtpDisabled = "As notificações por email exigem que o SMTP esteja habilitado nas configurações." +notifyOnly = "Um email será enviado sem a senha, informando ao usuário que um administrador a alterou." +submit = "Atualizar senha" +success = "Senha atualizada com sucesso" +error = "Falha ao atualizar a senha" + [workspace.people.emailInvite] tab = "Convite por email" description = "Digite ou cole emails abaixo, separados por vírgulas. Os usuários receberão credenciais de login por email." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Ao menos um endereço de email é obrigatório" submit = "Enviar convites" success = "usuário(s) convidado(s) com sucesso" -partialSuccess = "Alguns convites falharam" +partialFailure = "Alguns convites falharam" allFailed = "Falha ao convidar usuários" error = "Falha ao enviar convites" @@ -5541,7 +5696,7 @@ emailInvalid = "Digite um endereço de e-mail válido" title = "Informe seu e-mail" description = "Usaremos isso para enviar sua chave de licença e recibos." emailLabel = "Endereço de e-mail" -emailPlaceholder = "your@email.com" +emailPlaceholder = "seu@email.com" continue = "Continuar" modalTitle = "Começar - {{planName}}" @@ -5770,6 +5925,7 @@ subtitle = "Entre com sua conta do Stirling" [setup.selfhosted] title = "Entrar no servidor" subtitle = "Informe suas credenciais do servidor" +link = "ou conecte-se a uma conta auto-hospedada" [setup.server] title = "Conectar ao servidor" @@ -5788,6 +5944,14 @@ description = "Informe a URL completa do seu servidor self-hosted Stirling PDF" emptyUrl = "Informe uma URL de servidor" unreachable = "Não foi possível conectar ao servidor" testFailed = "Falha no teste de conexão" +configFetch = "Falha ao buscar a configuração do servidor. Verifique a URL e tente novamente." + +[setup.server.error.securityDisabled] +title = "Login não habilitado" +body = "Este servidor não tem o login habilitado. Para conectar-se a este servidor, você deve habilitar a autenticação:" +step1 = "Defina DOCKER_ENABLE_SECURITY=true no seu ambiente" +step2 = "Ou defina security.enableLogin=true em settings.yml" +step3 = "Reinicie o servidor" [setup.login] title = "Entrar" @@ -5797,13 +5961,20 @@ submit = "Login" signInWith = "Entrar com" oauthPending = "Abrindo o navegador para autenticação..." orContinueWith = "Ou continue com e-mail" +serverRequirement = "Observação: o servidor deve ter o login ativado." +showInstructions = "Como ativar?" +hideInstructions = "Ocultar instruções" +instructions = "Para ativar o login no seu servidor Stirling PDF:" +instructionsEnvVar = "Defina a variável de ambiente:" +instructionsOrYml = "Ou em settings.yml:" +instructionsRestart = "Em seguida, reinicie o servidor para que as alterações entrem em vigor." [setup.login.username] label = "Usuário" placeholder = "Digite seu usuário" [setup.login.email] -label = "Email" +label = "E-mail" placeholder = "Digite seu e-mail" [setup.login.password] @@ -5840,7 +6011,7 @@ paragraph = "Página de parágrafos" sparse = "Texto esparso" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automático" paragraph = "Parágrafo" singleLine = "Linha única" @@ -5853,6 +6024,7 @@ earlyAccess = "Acesso antecipado" reset = "Reverter alterações" downloadJson = "Baixar JSON" generatePdf = "Gerar PDF" +saveChanges = "Salvar alterações" [pdfTextEditor.options.autoScaleText] title = "Dimensionar texto automaticamente para caber nas caixas" @@ -5890,6 +6062,8 @@ alpha = "Este visualizador alpha ainda está evoluindo—certas fontes, cores, e [pdfTextEditor.empty] title = "Nenhum documento carregado" subtitle = "Carregue um PDF ou JSON para começar a editar texto." +dropzone = "Arraste e solte um arquivo PDF ou JSON aqui, ou clique para procurar" +dropzoneWithFiles = "Selecione um arquivo na aba Arquivos ou arraste e solte um arquivo PDF ou JSON aqui, ou clique para procurar" [pdfTextEditor.welcomeBanner] title = "Bem-vindo ao Editor de Texto PDF (Acesso antecipado)" diff --git a/frontend/public/locales/pt-PT/translation.toml b/frontend/public/locales/pt-PT/translation.toml index e530345e6..5e89a9612 100644 --- a/frontend/public/locales/pt-PT/translation.toml +++ b/frontend/public/locales/pt-PT/translation.toml @@ -131,7 +131,7 @@ unsupported = "Não suportado" [toolPanel] placeholder = "Escolha uma ferramenta para começar" -alpha = "Alpha" +alpha = "Alfa" premiumFeature = "Funcionalidade premium:" comingSoon = "Em breve:" @@ -163,6 +163,11 @@ unfavorite = "Remover dos favoritos" fullscreen = "Mudar para modo de ecrã inteiro" sidebar = "Mudar para modo de barra lateral" +[backendStartup] +notFoundTitle = "Backend não encontrado" +retry = "Tentar novamente" +unreachable = "A aplicação não consegue ligar-se ao backend neste momento. Verifique o estado do backend e a conectividade de rede e tente novamente." + [zipWarning] title = "Ficheiro ZIP grande" message = "Este ZIP contém {{count}} ficheiros. Extrair mesmo assim?" @@ -364,12 +369,12 @@ usageAnalytics = "Análise de utilização" [settings.policiesPrivacy] title = "Políticas e Privacidade" -legal = "Legal" +legal = "Jurídico" privacy = "Privacidade" [settings.developer] title = "Programador" -apiKeys = "API Keys" +apiKeys = "Chaves de API" [settings.tooltips] enableLoginFirst = "Ative primeiro o modo de login" @@ -912,6 +917,9 @@ desc = "Crie fluxos de trabalho de vários passos encadeando ações de PDF. Ide desc = "Sobrepõe PDFs em cima de outro PDF" title = "Sobrepor PDFs" +[home.pdfTextEditor] +title = "Editor de texto de PDF" +desc = "Edite texto e imagens existentes dentro de PDFs" [home.addText] tags = "texto,anotação,etiqueta" @@ -1217,7 +1225,7 @@ odtExt = "Texto OpenDocument (.odt)" pptExt = "PowerPoint (.pptx)" odpExt = "Apresentação OpenDocument (.odp)" txtExt = "Texto simples (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Formato de Texto Enriquecido (.rtf)" selectedFiles = "Ficheiros selecionados" noFileSelected = "Nenhum ficheiro selecionado. Use o painel de ficheiros para adicionar ficheiros." convertFiles = "Converter ficheiros" @@ -1360,7 +1368,7 @@ title = "Adicionar Marca de Água" desc = "Adicionar marcas de água de texto ou imagem a ficheiros PDF" completed = "Marca de água adicionada" submit = "Adicionar Marca de Água" -filenamePrefix = "watermarked" +filenamePrefix = "com-marca-de-água" [watermark.error] failed = "Ocorreu um erro ao adicionar a marca de água ao PDF." @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Assinatura desenhada" defaultImageLabel = "Assinatura carregada" defaultTextLabel = "Assinatura digitada" saveButton = "Guardar assinatura" +savePersonal = "Guardar como pessoal" +saveShared = "Guardar como partilhada" saveUnavailable = "Crie primeiro uma assinatura para a guardar." noChanges = "A assinatura atual já está guardada." +tempStorageTitle = "Armazenamento temporário do navegador" +tempStorageDescription = "As assinaturas são armazenadas apenas no seu navegador. Serão perdidas se limpar os dados do navegador ou mudar de navegador." +personalHeading = "Assinaturas pessoais" +sharedHeading = "Assinaturas partilhadas" +personalDescription = "Apenas você pode ver estas assinaturas." +sharedDescription = "Todos os utilizadores podem ver e usar estas assinaturas." [sign.saved.type] canvas = "Desenho" @@ -2841,7 +2857,7 @@ label = "Fator de escala" [adjustPageScale.pageSize] label = "Tamanho da página de destino" keep = "Manter tamanho original" -letter = "Letter" +letter = "Carta" legal = "Legal" [adjustPageScale.error] @@ -3020,6 +3036,91 @@ title = "Obter Informação do PDF" header = "Obter Informação do PDF" submit = "Obter Informação" downloadJson = "Transferir JSON" +processing = "A extrair informações..." +results = "Resultados" +noResults = "Execute a ferramenta para gerar um relatório." +downloads = "Transferências" +noneDetected = "Nenhum detetado" +indexTitle = "Índice" + +[getPdfInfo.report] +entryLabel = "Resumo completo das informações" +shortTitle = "Informações do PDF" + +[getPdfInfo.sections] +metadata = "Metadados" +formFields = "Campos do formulário" +basicInfo = "Informações básicas" +documentInfo = "Informações do documento" +compliance = "Conformidade" +encryption = "Encriptação" +permissions = "Permissões" +other = "Outros" +perPageInfo = "Informações por página" +tableOfContents = "Índice" + +[getPdfInfo.other] +attachments = "Anexos" +embeddedFiles = "Ficheiros incorporados" +javaScript = "JavaScript" +layers = "Camadas" +structureTree = "Árvore de estrutura" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Tamanho" +annotations = "Anotações" +images = "Imagens" +links = "Ligações" +fonts = "Tipos de letra" +xobjects = "Contagens de XObject" +multimedia = "Multimédia" + +[getPdfInfo.summary] +pages = "Páginas" +fileSize = "Tamanho do ficheiro" +pdfVersion = "Versão do PDF" +language = "Idioma" +title = "Resumo do PDF" +author = "Autor" +created = "Criado" +modified = "Modificado" +permsAll = "Todas as permissões autorizadas" +permsRestricted = "{{count}} restrições" +permsMixed = "Algumas permissões restritas" +hasCompliance = "Tem normas de conformidade" +noCompliance = "Sem normas de conformidade" +basic = "Informações básicas" +documentInfo = "Informações do documento" +securityTitle = "Estado de segurança" +technical = "Técnico" +overviewTitle = "Visão geral do PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF encriptado - Proteção por palavra-passe presente" +unencrypted = "PDF não encriptado - Sem proteção por palavra-passe" + +[getPdfInfo.summary.tech] +images = "Imagens" +fonts = "Tipos de letra" +formFields = "Campos do formulário" +embeddedFiles = "Ficheiros incorporados" +javaScript = "JavaScript" +layers = "Camadas" +bookmarks = "Marcadores" +multimedia = "Multimédia" + +[getPdfInfo.summary.overview] +untitled = "um documento sem título" +unknown = "Autor desconhecido" +text = "Este é um PDF de {{pages}} páginas intitulado {{title}}, criado por {{author}} (versão do PDF {{version}})." + +[getPdfInfo.error] +partial = "Alguns ficheiros não puderam ser processados." +unexpected = "Erro inesperado durante a extração." + +[getPdfInfo.status] +complete = "Extração concluída" [extractPage] tags = "extrair" @@ -3438,6 +3539,9 @@ signinTitle = "Por favor inicie sessão" ssoSignIn = "Iniciar sessão via Single Sign-On" oAuth2AutoCreateDisabled = "Criação Automática de Utilizador OAUTH2 Desativada" oAuth2AdminBlockedUser = "O registo ou login de utilizadores não registados está atualmente bloqueado. Por favor contacte o administrador." +oAuth2RequiresLicense = "O início de sessão via OAuth/SSO requer uma licença paga (Server ou Enterprise). Contacte o administrador para atualizar o seu plano." +saml2RequiresLicense = "O início de sessão SAML requer uma licença paga (Server ou Enterprise). Contacte o administrador para atualizar o seu plano." +maxUsersReached = "Foi atingido o número máximo de utilizadores da sua licença atual. Contacte o administrador para atualizar o seu plano ou adicionar mais lugares." oauth2RequestNotFound = "Pedido de autorização não encontrado" oauth2InvalidUserInfoResponse = "Resposta de Informação de Utilizador Inválida" oauth2invalidRequest = "Pedido Inválido" @@ -3846,14 +3950,17 @@ fitToWidth = "Ajustar à largura" actualSize = "Tamanho real" [viewer] +cannotPreviewFile = "Não é possível pré-visualizar o ficheiro" +dualPageView = "Vista de duas páginas" firstPage = "Primeira página" lastPage = "Última página" -previousPage = "Página anterior" nextPage = "Página seguinte" +onlyPdfSupported = "O visualizador só suporta ficheiros PDF. Este ficheiro parece ter um formato diferente." +previousPage = "Página anterior" +singlePageView = "Vista de página única" +unknownFile = "Ficheiro desconhecido" zoomIn = "Ampliar" zoomOut = "Reduzir" -singlePageView = "Vista de página única" -dualPageView = "Vista de duas páginas" [rightRail] closeSelected = "Fechar ficheiros selecionados" @@ -3877,6 +3984,7 @@ toggleSidebar = "Alternar barra lateral" exportSelected = "Exportar páginas selecionadas" toggleAnnotations = "Alternar visibilidade das anotações" annotationMode = "Alternar modo de anotação" +print = "Imprimir PDF" draw = "Desenhar" save = "Guardar" saveChanges = "Guardar alterações" @@ -4494,6 +4602,7 @@ description = "URL ou nome de ficheiro para o impressum (obrigatório em algumas title = "Premium e Enterprise" description = "Configurar a sua chave de licença premium ou enterprise." license = "Configuração de licença" +noInput = "Forneça uma chave ou ficheiro de licença" [admin.settings.premium.licenseKey] toggle = "Tem uma chave de licença ou ficheiro de certificado?" @@ -4511,6 +4620,25 @@ line1 = "Sobrescrever a sua chave de licença atual não pode ser anulado." line2 = "A sua licença anterior será perdida permanentemente, a menos que a tenha guardado noutro local." line3 = "Importante: mantenha as chaves de licença privadas e seguras. Nunca as partilhe publicamente." +[admin.settings.premium.inputMethod] +text = "Chave de licença" +file = "Ficheiro de certificado" + +[admin.settings.premium.file] +label = "Ficheiro de certificado de licença" +description = "Carregue o seu ficheiro de licença .lic ou .cert de compras offline" +choose = "Escolher ficheiro de licença" +selected = "Selecionado: {{filename}} ({{size}})" +successMessage = "Ficheiro de licença carregado e ativado com sucesso. Não é necessário reiniciar." + +[admin.settings.premium.currentLicense] +title = "Licença ativa" +file = "Origem: ficheiro de licença ({{path}})" +key = "Origem: chave de licença" +type = "Tipo: {{type}}" +noInput = "Forneça uma chave de licença ou carregue um ficheiro de certificado" +success = "Sucesso" + [admin.settings.premium.enabled] label = "Ativar funcionalidades premium" description = "Ativar verificações de chave de licença para funcionalidades pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} selecionado(s)" download = "Transferir" delete = "Eliminar" unsupported = "Não suportado" +active = "Ativo" addToUpload = "Adicionar ao carregamento" +closeFile = "Fechar ficheiro" deleteAll = "Eliminar tudo" loadingFiles = "A carregar ficheiros..." noFiles = "Não há ficheiros disponíveis" @@ -5132,7 +5262,7 @@ upgrade = "Atualizar agora →" freeTitle = "Licença do servidor" overLimitTitle = "É necessária licença de servidor" overLimitBody = "A nossa licença permite até {{freeTierLimit}} utilizadores gratuitos por servidor. Tem {{overLimitUserCopy}} utilizadores Stirling. Para continuar sem interrupções, atualize para o plano Stirling Server - lugares ilimitados, edição de texto em PDF e controlo total de administração por $99/servidor/mês." -freeBody = "A nossa licença Open-Core permite até {{freeTierLimit}} utilizadores gratuitos por servidor. Para escalar sem interrupções e obter acesso antecipado à nossa nova ferramenta de edição de texto PDF, recomendamos o plano Stirling Server - edição completa e lugares ilimitados por $99/servidor/mês." +freeBody = "O nosso licenciamento Open-Core permite até {{freeTierLimit}} utilizadores gratuitos por servidor. Para escalar sem interrupções, recomendamos o plano Stirling Server - lugares ilimitados e suporte SSO por $99/servidor/mês." [onboarding.desktopInstall] title = "Transferir" @@ -5237,6 +5367,31 @@ error = "Falha ao atualizar o estado do utilizador" success = "Utilizador eliminado com sucesso" error = "Falha ao eliminar utilizador" +[workspace.people.changePassword] +action = "Alterar palavra-passe" +title = "Alterar palavra-passe" +subtitle = "Atualizar a palavra-passe de" +newPassword = "Nova palavra-passe" +confirmPassword = "Confirmar palavra-passe" +placeholder = "Introduza uma nova palavra-passe" +confirmPlaceholder = "Introduza novamente a nova palavra-passe" +passwordRequired = "Por favor, introduza uma nova palavra-passe" +passwordMismatch = "As palavras-passe não coincidem" +generateRandom = "Gerar palavra-passe segura" +generatedPreview = "Palavra-passe gerada:" +copyTooltip = "Copiar para a área de transferência" +copiedToClipboard = "Palavra-passe copiada para a área de transferência" +copyFailed = "Falha ao copiar a palavra-passe" +sendEmail = "Enviar email ao utilizador sobre esta alteração" +includePassword = "Incluir a nova palavra-passe no email" +forcePasswordChange = "Obrigar o utilizador a alterar a palavra-passe no próximo início de sessão" +emailUnavailable = "O email deste utilizador não é um endereço válido. As notificações estão desativadas." +smtpDisabled = "As notificações por email requerem que o SMTP esteja ativado nas definições." +notifyOnly = "Será enviado um email sem a palavra-passe, informando o utilizador de que um administrador a alterou." +submit = "Guardar palavra-passe" +success = "Palavra-passe atualizada com sucesso" +error = "Falha ao atualizar a palavra-passe" + [workspace.people.emailInvite] tab = "Convite por email" description = "Escreva ou cole emails abaixo, separados por vírgulas. Os utilizadores receberão credenciais de login por email." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "É necessário pelo menos um endereço de email" submit = "Enviar convites" success = "utilizador(es) convidado(s) com sucesso" -partialSuccess = "Alguns convites falharam" +partialFailure = "Alguns convites falharam" allFailed = "Falha ao convidar utilizadores" error = "Falha ao enviar convites" @@ -5282,7 +5437,7 @@ submit = "Gerar link de convite" [workspace.people.inviteMode] username = "Nome de utilizador" email = "Email" -link = "Link" +link = "Ligação" emailDisabled = "Convites por email requerem configuração de SMTP e mail.enableInvites=true nas definições" [workspace.people.license] @@ -5770,6 +5925,7 @@ subtitle = "Inicie sessão com a sua conta Stirling" [setup.selfhosted] title = "Iniciar sessão no servidor" subtitle = "Introduza as credenciais do seu servidor" +link = "ou ligue-se a uma conta autoalojada" [setup.server] title = "Ligar ao servidor" @@ -5788,6 +5944,14 @@ description = "Introduza o URL completo do seu servidor Stirling PDF autoalojado emptyUrl = "Introduza um URL de servidor" unreachable = "Não foi possível ligar ao servidor" testFailed = "Falha no teste de ligação" +configFetch = "Falha ao obter a configuração do servidor. Verifique o URL e tente novamente." + +[setup.server.error.securityDisabled] +title = "Início de sessão não ativado" +body = "Este servidor não tem o início de sessão ativado. Para se ligar a este servidor, tem de ativar a autenticação:" +step1 = "Defina DOCKER_ENABLE_SECURITY=true no seu ambiente" +step2 = "Ou defina security.enableLogin=true no settings.yml" +step3 = "Reinicie o servidor" [setup.login] title = "Iniciar sessão" @@ -5797,6 +5961,13 @@ submit = "Iniciar sessão" signInWith = "Iniciar sessão com" oauthPending = "A abrir o navegador para autenticação..." orContinueWith = "Ou continuar com email" +serverRequirement = "Nota: O servidor deve ter o início de sessão ativado." +showInstructions = "Como ativar?" +hideInstructions = "Ocultar instruções" +instructions = "Para ativar o início de sessão no seu servidor Stirling PDF:" +instructionsEnvVar = "Defina a variável de ambiente:" +instructionsOrYml = "Ou em settings.yml:" +instructionsRestart = "Em seguida, reinicie o servidor para que as alterações tenham efeito." [setup.login.username] label = "Nome de utilizador" @@ -5840,7 +6011,7 @@ paragraph = "Página de parágrafos" sparse = "Texto disperso" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automático" paragraph = "Parágrafo" singleLine = "Linha única" @@ -5853,6 +6024,7 @@ earlyAccess = "Acesso antecipado" reset = "Repor alterações" downloadJson = "Transferir JSON" generatePdf = "Gerar PDF" +saveChanges = "Guardar alterações" [pdfTextEditor.options.autoScaleText] title = "Dimensionar texto automaticamente para caber nas caixas" @@ -5890,6 +6062,8 @@ alpha = "Este visualizador alpha ainda está a evoluir—certas fontes, cores, e [pdfTextEditor.empty] title = "Nenhum documento carregado" subtitle = "Carregue um ficheiro PDF ou JSON para começar a editar o conteúdo de texto." +dropzone = "Arraste e largue aqui um ficheiro PDF ou JSON, ou clique para procurar" +dropzoneWithFiles = "Selecione um ficheiro no separador Ficheiros, ou arraste e largue aqui um ficheiro PDF ou JSON, ou clique para procurar" [pdfTextEditor.welcomeBanner] title = "Bem-vindo ao PDF Text Editor (Acesso Antecipado)" @@ -5932,7 +6106,7 @@ warnings = "Avisos" suggestions = "Notas" currentPageFonts = "Fontes nesta página" allFonts = "Todas as fontes" -fallback = "fallback" +fallback = "alternativa" missing = "em falta" perfectMessage = "Todas as fontes podem ser reproduzidas na perfeição." warningMessage = "Algumas fontes podem não ser renderizadas corretamente." diff --git a/frontend/public/locales/ro-RO/translation.toml b/frontend/public/locales/ro-RO/translation.toml index 57c2ce481..927a10c5a 100644 --- a/frontend/public/locales/ro-RO/translation.toml +++ b/frontend/public/locales/ro-RO/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Eliminați din favorite" fullscreen = "Comutați la modul ecran complet" sidebar = "Comutați la modul bară laterală" +[backendStartup] +notFoundTitle = "Backend negăsit" +retry = "Reîncearcă" +unreachable = "Aplicația nu se poate conecta în prezent la backend. Verificați starea backend-ului și conexiunea la rețea, apoi încercați din nou." + [zipWarning] title = "Fișier ZIP mare" message = "Acest ZIP conține {{count}} fișiere. Extrageți oricum?" @@ -912,6 +917,9 @@ desc = "Construiți fluxuri cu mai mulți pași legând acțiuni PDF. Ideal pent desc = "Suprapune PDF-uri peste alt PDF" title = "Suprapune PDF-uri" +[home.pdfTextEditor] +title = "Editor de text PDF" +desc = "Editați textul și imaginile existente în PDF-uri" [home.addText] tags = "text,anotare,etichetă" @@ -1213,11 +1221,11 @@ pdfaDigitalSignatureWarning = "PDF-ul conține o semnătură digitală. Aceasta fileFormat = "Format fișier" wordDoc = "Document Word" wordDocExt = "Document Word (.docx)" -odtExt = "OpenDocument Text (.odt)" +odtExt = "Text OpenDocument (.odt)" pptExt = "PowerPoint (.pptx)" -odpExt = "OpenDocument Presentation (.odp)" +odpExt = "Prezentare OpenDocument (.odp)" txtExt = "Text simplu (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Format Rich Text (.rtf)" selectedFiles = "Fișiere selectate" noFileSelected = "Niciun fișier selectat. Folosiți panoul de fișiere pentru a adăuga fișiere." convertFiles = "Convertiți fișiere" @@ -1395,7 +1403,7 @@ height = "Spațiere pe înălțime" width = "Spațiere pe lățime" [watermark.alphabet] -roman = "Roman/Latin" +roman = "Romano/Latin" arabic = "Arabă" japanese = "Japoneză" korean = "Coreeană" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Semnătură desenată" defaultImageLabel = "Semnătură încărcată" defaultTextLabel = "Semnătură tastată" saveButton = "Salvează semnătura" +savePersonal = "Salvați ca personal" +saveShared = "Salvați ca partajat" saveUnavailable = "Creează mai întâi o semnătură pentru a o salva." noChanges = "Semnătura curentă este deja salvată." +tempStorageTitle = "Stocare temporară în browser" +tempStorageDescription = "Semnăturile sunt stocate doar în browserul dvs. Vor fi pierdute dacă ștergeți datele browserului sau schimbați browserul." +personalHeading = "Semnături personale" +sharedHeading = "Semnături partajate" +personalDescription = "Doar dvs. puteți vedea aceste semnături." +sharedDescription = "Toți utilizatorii pot vedea și utiliza aceste semnături." [sign.saved.type] canvas = "Desen" @@ -3020,6 +3036,91 @@ title = "Obține Informații despre PDF" header = "Obține Informații despre PDF" submit = "Obține Informații" downloadJson = "Descarcă JSON" +processing = "Se extrag informațiile..." +results = "Rezultate" +noResults = "Rulați instrumentul pentru a genera un raport." +downloads = "Descărcări" +noneDetected = "Nu s-a detectat nimic" +indexTitle = "Index" + +[getPdfInfo.report] +entryLabel = "Rezumat complet al informațiilor" +shortTitle = "Informații PDF" + +[getPdfInfo.sections] +metadata = "Metadate" +formFields = "Câmpuri de formular" +basicInfo = "Informații de bază" +documentInfo = "Informații despre document" +compliance = "Conformitate" +encryption = "Criptare" +permissions = "Permisiuni" +other = "Altele" +perPageInfo = "Informații pe pagină" +tableOfContents = "Cuprins" + +[getPdfInfo.other] +attachments = "Atașamente" +embeddedFiles = "Fișiere încorporate" +javaScript = "JavaScript" +layers = "Straturi" +structureTree = "Arbore de structură" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Dimensiune" +annotations = "Adnotări" +images = "Imagini" +links = "Linkuri" +fonts = "Fonturi" +xobjects = "Număr de XObject-uri" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Pagini" +fileSize = "Dimensiune fișier" +pdfVersion = "Versiune PDF" +language = "Limbă" +title = "Rezumat PDF" +author = "Autor" +created = "Creat" +modified = "Modificat" +permsAll = "Toate permisiunile sunt acordate" +permsRestricted = "{{count}} restricții" +permsMixed = "Unele permisiuni sunt restricționate" +hasCompliance = "Are standarde de conformitate" +noCompliance = "Fără standarde de conformitate" +basic = "Informații de bază" +documentInfo = "Informații despre document" +securityTitle = "Starea securității" +technical = "Tehnic" +overviewTitle = "Prezentare generală PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF criptat - Protejat cu parolă" +unencrypted = "PDF necriptat - Fără protecție prin parolă" + +[getPdfInfo.summary.tech] +images = "Imagini" +fonts = "Fonturi" +formFields = "Câmpuri de formular" +embeddedFiles = "Fișiere încorporate" +javaScript = "JavaScript" +layers = "Straturi" +bookmarks = "Marcaje" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "un document fără titlu" +unknown = "Autor necunoscut" +text = "Acesta este un PDF de {{pages}} pagini, intitulat {{title}}, creat de {{author}} (versiune PDF {{version}})." + +[getPdfInfo.error] +partial = "Unele fișiere nu au putut fi procesate." +unexpected = "Eroare neașteptată în timpul extragerii." + +[getPdfInfo.status] +complete = "Extragere finalizată" [extractPage] tags = "extrage" @@ -3438,6 +3539,9 @@ signinTitle = "Te rugăm să te autentifici" ssoSignIn = "Conectare prin conectare unică" oAuth2AutoCreateDisabled = "OAUTH2 Creare automată utilizator dezactivată" oAuth2AdminBlockedUser = "Înregistrarea sau conectarea utilizatorilor neînregistrați este în prezent blocată. Te rugăm să contactezi administratorul." +oAuth2RequiresLicense = "Autentificarea OAuth/SSO necesită o licență plătită (Server sau Enterprise). Contactați administratorul pentru a vă actualiza planul." +saml2RequiresLicense = "Autentificarea SAML necesită o licență plătită (Server sau Enterprise). Contactați administratorul pentru a vă actualiza planul." +maxUsersReached = "Numărul maxim de utilizatori a fost atins pentru licența curentă. Contactați administratorul pentru a vă actualiza planul sau pentru a adăuga mai multe locuri." oauth2RequestNotFound = "Cererea de autorizare nu a fost găsită" oauth2InvalidUserInfoResponse = "Răspuns Invalid la Informațiile Utilizatorului" oauth2invalidRequest = "Cerere Invalidă" @@ -3846,14 +3950,17 @@ fitToWidth = "Potriviți la lățime" actualSize = "Dimensiune reală" [viewer] +cannotPreviewFile = "Nu se poate previzualiza fișierul" +dualPageView = "Vizualizare cu două pagini" firstPage = "Prima pagină" lastPage = "Ultima pagină" -previousPage = "Pagina anterioară" nextPage = "Pagina următoare" +onlyPdfSupported = "Vizualizatorul acceptă doar fișiere PDF. Acest fișier pare a fi într-un format diferit." +previousPage = "Pagina anterioară" +singlePageView = "Vizualizare cu o singură pagină" +unknownFile = "Fișier necunoscut" zoomIn = "Măriți" zoomOut = "Micșorați" -singlePageView = "Vizualizare cu o singură pagină" -dualPageView = "Vizualizare cu două pagini" [rightRail] closeSelected = "Închideți fișierele selectate" @@ -3877,6 +3984,7 @@ toggleSidebar = "Comutați bara laterală" exportSelected = "Exportați paginile selectate" toggleAnnotations = "Comutați vizibilitatea adnotărilor" annotationMode = "Comutați modul de adnotare" +print = "Imprimați PDF" draw = "Desenați" save = "Salvați" saveChanges = "Salvați modificările" @@ -3925,7 +4033,7 @@ files = "Fișiere" activity = "Jurnal" help = "Ajutor" account = "Cont" -config = "Config" +config = "Configurare" settings = "Setări" adminSettings = "Setări admin" allTools = "All Tools" @@ -4153,7 +4261,7 @@ description = "Urmărește acțiunile utilizatorilor și evenimentele de sistem [admin.settings.security.audit.level] label = "Nivel audit" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=OPRIT, 1=DE BAZĂ, 2=STANDARD, 3=DETALIAT" [admin.settings.security.audit.retentionDays] label = "Păstrare audit (zile)" @@ -4494,6 +4602,7 @@ description = "URL sau nume de fișier către impressum (necesar în unele juris title = "Premium și Enterprise" description = "Configurați cheia de licență premium sau enterprise." license = "Configurare licență" +noInput = "Vă rugăm să furnizați o cheie sau un fișier de licență" [admin.settings.premium.licenseKey] toggle = "Ai o cheie de licență sau un fișier certificat?" @@ -4511,6 +4620,25 @@ line1 = "Suprascrierea cheii de licență curente nu poate fi anulată." line2 = "Licența anterioară va fi pierdută definitiv dacă nu ai o copie de rezervă." line3 = "Important: Păstrează cheile de licență private și în siguranță. Nu le distribui public." +[admin.settings.premium.inputMethod] +text = "Cheie de licență" +file = "Fișier de certificat" + +[admin.settings.premium.file] +label = "Fișier certificat de licență" +description = "Încărcați fișierul de licență .lic sau .cert din achizițiile offline" +choose = "Alegeți fișierul de licență" +selected = "Selectat: {{filename}} ({{size}})" +successMessage = "Fișierul de licență a fost încărcat și activat cu succes. Nu este necesară repornirea." + +[admin.settings.premium.currentLicense] +title = "Licență activă" +file = "Sursă: Fișier de licență ({{path}})" +key = "Sursă: Cheie de licență" +type = "Tip: {{type}}" +noInput = "Vă rugăm să furnizați o cheie de licență sau să încărcați un fișier de certificat" +success = "Succes" + [admin.settings.premium.enabled] label = "Activează funcțiile Premium" description = "Activează verificările cheii de licență pentru funcțiile pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} selectate" download = "Descarcă" delete = "Șterge" unsupported = "Nesuportat" +active = "Activ" addToUpload = "Adăugați la încărcare" +closeFile = "Închide fișierul" deleteAll = "Ștergeți tot" loadingFiles = "Se încarcă fișierele..." noFiles = "Nu există fișiere disponibile" @@ -5132,7 +5262,7 @@ upgrade = "Fă upgrade acum →" freeTitle = "Licență server" overLimitTitle = "Necesită licență de server" overLimitBody = "Politica noastră de licențiere permite până la {{freeTierLimit}} utilizatori gratuit per server. Ai {{overLimitUserCopy}} utilizatori Stirling. Pentru a continua fără întreruperi, fă upgrade la planul Stirling Server - locuri nelimitate, editare text PDF și control complet de admin pentru $99/server/lună." -freeBody = "Licențierea noastră Open-Core permite până la {{freeTierLimit}} utilizatori gratuit per server. Pentru a scala fără întreruperi și a primi acces timpuriu la noul nostru instrument de editare text PDF, recomandăm planul Stirling Server - editare completă și locuri nelimitate pentru $99/server/lună." +freeBody = "Licențierea noastră Open-Core permite până la {{freeTierLimit}} utilizatori gratuit per server. Pentru scalare fără întreruperi, recomandăm planul Stirling Server - locuri nelimitate și suport SSO pentru $99/server/lună." [onboarding.desktopInstall] title = "Descărcare" @@ -5237,6 +5367,31 @@ error = "Actualizarea stării utilizatorului a eșuat" success = "Utilizator șters cu succes" error = "Ștergerea utilizatorului a eșuat" +[workspace.people.changePassword] +action = "Schimbă parola" +title = "Schimbă parola" +subtitle = "Actualizați parola pentru" +newPassword = "Parolă nouă" +confirmPassword = "Confirmă parola" +placeholder = "Introduceți o parolă nouă" +confirmPlaceholder = "Reintroduceți parola nouă" +passwordRequired = "Introduceți o parolă nouă" +passwordMismatch = "Parolele nu coincid" +generateRandom = "Generează o parolă sigură" +generatedPreview = "Parolă generată:" +copyTooltip = "Copiază în clipboard" +copiedToClipboard = "Parola a fost copiată în clipboard" +copyFailed = "Copierea parolei a eșuat" +sendEmail = "Trimite un email utilizatorului despre această modificare" +includePassword = "Include parola nouă în email" +forcePasswordChange = "Forțează utilizatorul să schimbe parola la următoarea autentificare" +emailUnavailable = "Emailul acestui utilizator nu este o adresă de email validă. Notificările sunt dezactivate." +smtpDisabled = "Notificările prin email necesită activarea SMTP în setări." +notifyOnly = "Se va trimite un email fără parolă, informând utilizatorul că un administrator a schimbat-o." +submit = "Actualizează parola" +success = "Parola a fost actualizată cu succes" +error = "Actualizarea parolei a eșuat" + [workspace.people.emailInvite] tab = "Invitație prin email" description = "Tastați sau lipiți emailuri mai jos, separate prin virgule. Utilizatorii vor primi datele de conectare prin email." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Este necesară cel puțin o adresă de email" submit = "Trimiteți invitații" success = "utilizator(i) invitați cu succes" -partialSuccess = "Unele invitații au eșuat" +partialFailure = "Unele invitații au eșuat" allFailed = "Invitarea utilizatorilor a eșuat" error = "Trimiterea invitațiilor a eșuat" @@ -5380,7 +5535,7 @@ submit = "Schimbă echipa" currency = "Monedă" popular = "Popular" current = "Plan curent" -upgrade = "Upgrade" +upgrade = "Actualizează" contact = "Contactează-ne" customPricing = "Personalizat" showComparison = "Compară toate funcțiile" @@ -5770,6 +5925,7 @@ subtitle = "Autentifică-te cu contul tău Stirling" [setup.selfhosted] title = "Autentifică-te pe server" subtitle = "Introdu acreditările serverului tău" +link = "sau conectați-vă la un cont self-hosted" [setup.server] title = "Conectează-te la server" @@ -5788,6 +5944,14 @@ description = "Introdu URL-ul complet al serverului tău Stirling PDF găzduit l emptyUrl = "Te rugăm să introduci un URL de server" unreachable = "Nu s-a putut conecta la server" testFailed = "Testul de conexiune a eșuat" +configFetch = "Nu s-a putut prelua configurația serverului. Verificați URL-ul și încercați din nou." + +[setup.server.error.securityDisabled] +title = "Autentificarea nu este activată" +body = "Acest server nu are autentificarea activată. Pentru a vă conecta la acest server, trebuie să activați autentificarea:" +step1 = "Setați DOCKER_ENABLE_SECURITY=true în mediul dvs." +step2 = "Sau setați security.enableLogin=true în settings.yml" +step3 = "Reporniți serverul" [setup.login] title = "Autentificare" @@ -5797,6 +5961,13 @@ submit = "Autentificare" signInWith = "Autentifică-te cu" oauthPending = "Se deschide browserul pentru autentificare..." orContinueWith = "Sau continuă cu email" +serverRequirement = "Notă: Serverul trebuie să aibă autentificarea activată." +showInstructions = "Cum se activează?" +hideInstructions = "Ascunde instrucțiunile" +instructions = "Pentru a activa autentificarea pe serverul dvs. Stirling PDF:" +instructionsEnvVar = "Setați variabila de mediu:" +instructionsOrYml = "Sau în settings.yml:" +instructionsRestart = "Apoi reporniți serverul pentru ca modificările să intre în vigoare." [setup.login.username] label = "Utilizator" @@ -5853,6 +6024,7 @@ earlyAccess = "Acces timpuriu" reset = "Resetați modificările" downloadJson = "Descărcați JSON" generatePdf = "Generați PDF" +saveChanges = "Salvează modificările" [pdfTextEditor.options.autoScaleText] title = "Scalare automată a textului pentru a se potrivi în casete" @@ -5890,6 +6062,8 @@ alpha = "Acest vizualizator alpha este încă în dezvoltare — anumite fonturi [pdfTextEditor.empty] title = "Niciun document încărcat" subtitle = "Încărcați un fișier PDF sau JSON pentru a începe editarea conținutului text." +dropzone = "Glisați și fixați aici un fișier PDF sau JSON, sau faceți clic pentru a răsfoi" +dropzoneWithFiles = "Selectați un fișier din fila Fișiere sau glisați și fixați aici un fișier PDF sau JSON, sau faceți clic pentru a răsfoi" [pdfTextEditor.welcomeBanner] title = "Bine ați venit la Editor text PDF (Acces timpuriu)" @@ -5932,7 +6106,7 @@ warnings = "Avertizări" suggestions = "Note" currentPageFonts = "Fonturi pe această pagină" allFonts = "Toate fonturile" -fallback = "fallback" +fallback = "rezervă" missing = "lipsește" perfectMessage = "Toate fonturile pot fi redate perfect." warningMessage = "Unele fonturi pot să nu fie redate corect." diff --git a/frontend/public/locales/ru-RU/translation.toml b/frontend/public/locales/ru-RU/translation.toml index 359bb5783..600c7dfc0 100644 --- a/frontend/public/locales/ru-RU/translation.toml +++ b/frontend/public/locales/ru-RU/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Удалить из избранного" fullscreen = "Переключиться в полноэкранный режим" sidebar = "Переключиться в режим боковой панели" +[backendStartup] +notFoundTitle = "Серверная часть не найдена" +retry = "Повторить" +unreachable = "Приложение сейчас не может подключиться к серверной части. Проверьте состояние серверной части и подключение к сети, затем повторите попытку." + [zipWarning] title = "Большой ZIP-файл" message = "Этот ZIP содержит {{count}} файлов. Все равно извлечь?" @@ -912,6 +917,9 @@ desc = "Создавайте многошаговые процессы, связ desc = "Наложить один PDF поверх другого" title = "Наложение PDF" +[home.pdfTextEditor] +title = "Редактор текста PDF" +desc = "Редактируйте существующий текст и изображения внутри PDF-файлов" [home.addText] tags = "текст,аннотация,ярлык" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Рисованная подпись" defaultImageLabel = "Загруженная подпись" defaultTextLabel = "Введённая подпись" saveButton = "Сохранить подпись" +savePersonal = "Сохранить как личную" +saveShared = "Сохранить как общую" saveUnavailable = "Сначала создайте подпись, чтобы сохранить её." noChanges = "Текущая подпись уже сохранена." +tempStorageTitle = "Временное хранилище браузера" +tempStorageDescription = "Подписи сохраняются только в вашем браузере. Они будут потеряны, если вы очистите данные браузера или смените браузер." +personalHeading = "Личные подписи" +sharedHeading = "Общие подписи" +personalDescription = "Эти подписи видны только вам." +sharedDescription = "Все пользователи могут видеть и использовать эти подписи." [sign.saved.type] canvas = "Рисунок" @@ -3020,6 +3036,91 @@ title = "Получить информацию о PDF" header = "Получить информацию о PDF" submit = "Получить информацию" downloadJson = "Скачать JSON" +processing = "Извлечение информации..." +results = "Результаты" +noResults = "Запустите инструмент, чтобы создать отчет." +downloads = "Загрузки" +noneDetected = "Ничего не обнаружено" +indexTitle = "Индекс" + +[getPdfInfo.report] +entryLabel = "Полная сводка информации" +shortTitle = "Информация о PDF" + +[getPdfInfo.sections] +metadata = "Метаданные" +formFields = "Поля формы" +basicInfo = "Основная информация" +documentInfo = "Сведения о документе" +compliance = "Соответствие" +encryption = "Шифрование" +permissions = "Разрешения" +other = "Другое" +perPageInfo = "Информация по страницам" +tableOfContents = "Оглавление" + +[getPdfInfo.other] +attachments = "Вложения" +embeddedFiles = "Встроенные файлы" +javaScript = "JavaScript" +layers = "Слои" +structureTree = "Дерево структуры" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Размер" +annotations = "Аннотации" +images = "Изображения" +links = "Ссылки" +fonts = "Шрифты" +xobjects = "Количество XObject" +multimedia = "Мультимедиа" + +[getPdfInfo.summary] +pages = "Страницы" +fileSize = "Размер файла" +pdfVersion = "Версия PDF" +language = "Язык" +title = "Сводка по PDF" +author = "Автор" +created = "Создано" +modified = "Изменено" +permsAll = "Все разрешения доступны" +permsRestricted = "{{count}} ограничений" +permsMixed = "Некоторые разрешения ограничены" +hasCompliance = "Есть стандарты соответствия" +noCompliance = "Нет стандартов соответствия" +basic = "Основная информация" +documentInfo = "Сведения о документе" +securityTitle = "Статус безопасности" +technical = "Технические сведения" +overviewTitle = "Обзор PDF" + +[getPdfInfo.summary.security] +encrypted = "Зашифрованный PDF — используется защита паролем" +unencrypted = "Нешифрованный PDF — защита паролем отсутствует" + +[getPdfInfo.summary.tech] +images = "Изображения" +fonts = "Шрифты" +formFields = "Поля формы" +embeddedFiles = "Встроенные файлы" +javaScript = "JavaScript" +layers = "Слои" +bookmarks = "Закладки" +multimedia = "Мультимедиа" + +[getPdfInfo.summary.overview] +untitled = "документ без названия" +unknown = "Неизвестный автор" +text = "Это PDF из {{pages}} страниц под названием {{title}}, создан {{author}} (версия PDF {{version}})." + +[getPdfInfo.error] +partial = "Некоторые файлы не удалось обработать." +unexpected = "Непредвиденная ошибка при извлечении." + +[getPdfInfo.status] +complete = "Извлечение завершено" [extractPage] tags = "извлечение" @@ -3438,6 +3539,9 @@ signinTitle = "Пожалуйста, войдите" ssoSignIn = "Вход через единый вход" oAuth2AutoCreateDisabled = "Автоматическое создание пользователей OAuth2 отключено" oAuth2AdminBlockedUser = "Регистрация или вход незарегистрированных пользователей в настоящее время заблокированы. Обратитесь к администратору." +oAuth2RequiresLicense = "Вход через OAuth/SSO требует платную лицензию (Server или Enterprise). Пожалуйста, свяжитесь с администратором, чтобы обновить ваш план." +saml2RequiresLicense = "Вход через SAML требует платную лицензию (Server или Enterprise). Пожалуйста, свяжитесь с администратором, чтобы обновить ваш план." +maxUsersReached = "Достигнуто максимальное количество пользователей для вашей текущей лицензии. Пожалуйста, свяжитесь с администратором, чтобы обновить ваш план или добавить места." oauth2RequestNotFound = "Запрос авторизации не найден" oauth2InvalidUserInfoResponse = "Недействительный ответ с информацией о пользователе" oauth2invalidRequest = "Недействительный запрос" @@ -3846,14 +3950,17 @@ fitToWidth = "По ширине" actualSize = "Фактический размер" [viewer] +cannotPreviewFile = "Не удаётся просмотреть файл" +dualPageView = "Двухстраничный вид" firstPage = "Первая страница" lastPage = "Последняя страница" -previousPage = "Предыдущая страница" nextPage = "Следующая страница" +onlyPdfSupported = "Просмотрщик поддерживает только PDF-файлы. Похоже, этот файл другого формата." +previousPage = "Предыдущая страница" +singlePageView = "Одностраничный вид" +unknownFile = "Неизвестный файл" zoomIn = "Увеличить" zoomOut = "Уменьшить" -singlePageView = "Одностраничный вид" -dualPageView = "Двухстраничный вид" [rightRail] closeSelected = "Закрыть выбранные файлы" @@ -3877,6 +3984,7 @@ toggleSidebar = "Показать/скрыть боковую панель" exportSelected = "Экспортировать выбранные страницы" toggleAnnotations = "Показать/скрыть аннотации" annotationMode = "Переключить режим аннотаций" +print = "Печать PDF" draw = "Рисовать" save = "Сохранить" saveChanges = "Сохранить изменения" @@ -4487,13 +4595,14 @@ label = "Политика cookie" description = "URL или имя файла для политики cookie" [admin.settings.legal.impressum] -label = "Impressum" +label = "Юридическая информация" description = "URL или имя файла для impressum (требуется в некоторых юрисдикциях)" [admin.settings.premium] title = "Премиум и Enterprise" description = "Настройте ключ лицензии премиум или enterprise." license = "Конфигурация лицензии" +noInput = "Укажите лицензионный ключ или файл" [admin.settings.premium.licenseKey] toggle = "Есть лицензионный ключ или файл сертификата?" @@ -4511,6 +4620,25 @@ line1 = "Перезапись текущего лицензионного клю line2 = "Предыдущая лицензия будет безвозвратно утеряна, если вы не сделали резервную копию где‑то ещё." line3 = "Важно: храните лицензионные ключи в секрете и безопасности. Никогда не публикуйте их." +[admin.settings.premium.inputMethod] +text = "Лицензионный ключ" +file = "Файл сертификата" + +[admin.settings.premium.file] +label = "Файл лицензионного сертификата" +description = "Загрузите файл лицензии .lic или .cert из офлайн-покупки" +choose = "Выберите файл лицензии" +selected = "Выбрано: {{filename}} ({{size}})" +successMessage = "Файл лицензии успешно загружен и активирован. Перезапуск не требуется." + +[admin.settings.premium.currentLicense] +title = "Активная лицензия" +file = "Источник: файл лицензии ({{path}})" +key = "Источник: лицензионный ключ" +type = "Тип: {{type}}" +noInput = "Укажите лицензионный ключ или загрузите файл сертификата" +success = "Успешно" + [admin.settings.premium.enabled] label = "Включить премиум-функции" description = "Включить проверку лицензии для pro/enterprise функций" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} выбрано" download = "Скачать" delete = "Удалить" unsupported = "Не поддерживается" +active = "Активный" addToUpload = "Добавить к загрузке" +closeFile = "Закрыть файл" deleteAll = "Удалить все" loadingFiles = "Загрузка файлов..." noFiles = "Нет доступных файлов" @@ -4964,7 +5094,7 @@ description = "Привяжите аккаунт, чтобы сохранить socialLogin = "Обновить через соцсеть" linkWith = "Привязать к" emailPassword = "или введите email и пароль" -email = "Email" +email = "Эл. почта" emailPlaceholder = "Введите ваш email" password = "Пароль (необязательно)" passwordPlaceholder = "Задайте пароль" @@ -5132,7 +5262,7 @@ upgrade = "Обновить сейчас →" freeTitle = "Серверная лицензия" overLimitTitle = "Требуется серверная лицензия" overLimitBody = "Наша лицензия допускает до {{freeTierLimit}} пользователей бесплатно на сервер. У вас {{overLimitUserCopy}} пользователей Stirling. Чтобы продолжить без перебоев, перейдите на тариф Stirling Server — неограниченные места, редактирование текста в PDF и полный админ‑контроль за $99/server/mo." -freeBody = "Наша лицензия Open-Core допускает до {{freeTierLimit}} пользователей бесплатно на сервер. Чтобы масштабироваться без ограничений и раньше получить доступ к новому инструменту редактирования текста в PDF, рекомендуем тариф Stirling Server — полный редактор и неограниченные места за $99/server/mo." +freeBody = "Наша лицензия Open-Core позволяет бесплатно использовать до {{freeTierLimit}} пользователей на сервер. Для бесшовного масштабирования мы рекомендуем план Stirling Server - неограниченное число мест и поддержка SSO за $99/сервер/мес." [onboarding.desktopInstall] title = "Скачать" @@ -5237,6 +5367,31 @@ error = "Не удалось обновить статус пользовате success = "Пользователь успешно удален" error = "Не удалось удалить пользователя" +[workspace.people.changePassword] +action = "Изменить пароль" +title = "Изменить пароль" +subtitle = "Обновить пароль для" +newPassword = "Новый пароль" +confirmPassword = "Подтвердите пароль" +placeholder = "Введите новый пароль" +confirmPlaceholder = "Повторно введите новый пароль" +passwordRequired = "Пожалуйста, введите новый пароль" +passwordMismatch = "Пароли не совпадают" +generateRandom = "Сгенерировать безопасный пароль" +generatedPreview = "Сгенерированный пароль:" +copyTooltip = "Копировать в буфер обмена" +copiedToClipboard = "Пароль скопирован в буфер обмена" +copyFailed = "Не удалось скопировать пароль" +sendEmail = "Отправить пользователю письмо об этом изменении" +includePassword = "Включить новый пароль в письмо" +forcePasswordChange = "Потребовать смену пароля при следующем входе" +emailUnavailable = "Адрес электронной почты этого пользователя недействителен. Уведомления отключены." +smtpDisabled = "Для уведомлений по электронной почте необходимо включить SMTP в настройках." +notifyOnly = "Будет отправлено письмо без пароля с уведомлением, что администратор его изменил." +submit = "Обновить пароль" +success = "Пароль успешно обновлён" +error = "Не удалось обновить пароль" + [workspace.people.emailInvite] tab = "Приглашение по email" description = "Введите или вставьте ниже адреса email, разделяя их запятыми. Пользователи получат учетные данные для входа по email." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Требуется хотя бы один адрес email" submit = "Отправить приглашения" success = "Пользователи успешно приглашены" -partialSuccess = "Некоторые приглашения не отправлены" +partialFailure = "Некоторые приглашения не удалось отправить" allFailed = "Не удалось пригласить пользователей" error = "Не удалось отправить приглашения" @@ -5770,6 +5925,7 @@ subtitle = "Войдите в аккаунт Stirling" [setup.selfhosted] title = "Вход на сервер" subtitle = "Введите учётные данные сервера" +link = "или подключитесь к самохостируемой учётной записи" [setup.server] title = "Подключение к серверу" @@ -5788,6 +5944,14 @@ description = "Введите полный URL вашего self-hosted серв emptyUrl = "Введите URL сервера" unreachable = "Не удалось подключиться к серверу" testFailed = "Тест подключения не пройден" +configFetch = "Не удалось получить конфигурацию сервера. Проверьте URL и попробуйте ещё раз." + +[setup.server.error.securityDisabled] +title = "Вход не включён" +body = "На этом сервере вход не включён. Чтобы подключиться к этому серверу, необходимо включить аутентификацию:" +step1 = "Установите DOCKER_ENABLE_SECURITY=true в вашей среде" +step2 = "Или установите security.enableLogin=true в settings.yml" +step3 = "Перезапустите сервер" [setup.login] title = "Вход" @@ -5797,13 +5961,20 @@ submit = "Войти" signInWith = "Войти через" oauthPending = "Открываем браузер для аутентификации..." orContinueWith = "Или продолжить по email" +serverRequirement = "Примечание: на сервере должен быть включён вход в систему." +showInstructions = "Как включить?" +hideInstructions = "Скрыть инструкции" +instructions = "Чтобы включить вход в систему на вашем сервере Stirling PDF:" +instructionsEnvVar = "Установите переменную окружения:" +instructionsOrYml = "Или в settings.yml:" +instructionsRestart = "Затем перезапустите сервер, чтобы изменения вступили в силу." [setup.login.username] label = "Имя пользователя" placeholder = "Введите имя пользователя" [setup.login.email] -label = "Email" +label = "Эл. почта" placeholder = "Введите email" [setup.login.password] @@ -5853,6 +6024,7 @@ earlyAccess = "Ранний доступ" reset = "Сбросить изменения" downloadJson = "Скачать JSON" generatePdf = "Сформировать PDF" +saveChanges = "Сохранить изменения" [pdfTextEditor.options.autoScaleText] title = "Автоматически подгонять текст по рамке" @@ -5890,6 +6062,8 @@ alpha = "Этот альфа‑просмотрщик ещё развивает [pdfTextEditor.empty] title = "Документ не загружен" subtitle = "Загрузите файл PDF или JSON, чтобы начать редактирование текста." +dropzone = "Перетащите сюда файл PDF или JSON, или нажмите, чтобы выбрать" +dropzoneWithFiles = "Выберите файл на вкладке «Файлы» или перетащите сюда файл PDF или JSON, или нажмите, чтобы выбрать" [pdfTextEditor.welcomeBanner] title = "Добро пожаловать в редактор текста PDF (ранний доступ)" diff --git a/frontend/public/locales/sk-SK/translation.toml b/frontend/public/locales/sk-SK/translation.toml index 0b6633ff2..64d3da204 100644 --- a/frontend/public/locales/sk-SK/translation.toml +++ b/frontend/public/locales/sk-SK/translation.toml @@ -99,7 +99,7 @@ visitGithub = "Navštíviť GitHub repozitár" donate = "Darovať" color = "Farba" sponsor = "Sponzorovať" -info = "Info" +info = "Informácie" pro = "Pro" page = "Strana" pages = "Strany" @@ -163,6 +163,11 @@ unfavorite = "Odstrániť z obľúbených" fullscreen = "Prepnúť na režim celej obrazovky" sidebar = "Prepnúť na režim bočného panela" +[backendStartup] +notFoundTitle = "Backend sa nenašiel" +retry = "Skúsiť znova" +unreachable = "Aplikácia sa momentálne nedokáže pripojiť k backendu. Overte stav backendu a sieťové pripojenie, potom to skúste znova." + [zipWarning] title = "Veľký ZIP súbor" message = "Tento ZIP obsahuje {{count}} súborov. Aj tak rozbaliť?" @@ -274,7 +279,7 @@ iAgreeToThe = "Súhlasím so všetkými" terms = "Podmienkami používania" accessibility = "Prístupnosť" cookie = "Zásady používania súborov cookie" -impressum = "Impressum" +impressum = "Impresum" showCookieBanner = "Predvoľby súborov cookie" [pipeline] @@ -513,7 +518,7 @@ syncToAccount = "Synchronizovať účet <- Prehliadač" [adminUserSettings] title = "Nastavenia kontroly používateľov" header = "Admin nastavenia kontroly používateľov" -admin = "Admin" +admin = "Administrátor" user = "Používateľ" addUser = "Pridať nového používateľa" deleteUser = "Odstrániť používateľa" @@ -912,6 +917,9 @@ desc = "Stavať viacstupňové pracovné postupy spájaním akcií PDF. Ideálne desc = "Prekrýva PDF súbory na iný PDF" title = "Prekrývanie PDF" +[home.pdfTextEditor] +title = "Editor textu PDF" +desc = "Upravujte existujúci text a obrázky v PDF" [home.addText] tags = "text,anotácia,štítok" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Kreslený podpis" defaultImageLabel = "Nahraný podpis" defaultTextLabel = "Napísaný podpis" saveButton = "Uložiť podpis" +savePersonal = "Uložiť osobné" +saveShared = "Uložiť zdieľané" saveUnavailable = "Najprv vytvorte podpis, aby ste ho mohli uložiť." noChanges = "Aktuálny podpis je už uložený." +tempStorageTitle = "Dočasné úložisko prehliadača" +tempStorageDescription = "Podpisy sú uložené iba vo vašom prehliadači. Pri vymazaní údajov prehliadača alebo pri zmene prehliadača sa stratia." +personalHeading = "Osobné podpisy" +sharedHeading = "Zdieľané podpisy" +personalDescription = "Tieto podpisy vidíte iba vy." +sharedDescription = "Všetci používatelia môžu tieto podpisy vidieť a používať." [sign.saved.type] canvas = "Kresba" @@ -3020,6 +3036,91 @@ title = "Získať informácie o PDF" header = "Získať informácie o PDF" submit = "Získať info" downloadJson = "Stiahnuť JSON" +processing = "Extrahujú sa informácie..." +results = "Výsledky" +noResults = "Spustite nástroj na vygenerovanie prehľadu." +downloads = "Sťahovania" +noneDetected = "Nič nebolo zistené" +indexTitle = "Index" + +[getPdfInfo.report] +entryLabel = "Úplné zhrnutie informácií" +shortTitle = "Informácie o PDF" + +[getPdfInfo.sections] +metadata = "Metadáta" +formFields = "Polia formulára" +basicInfo = "Základné informácie" +documentInfo = "Informácie o dokumente" +compliance = "Súlad" +encryption = "Šifrovanie" +permissions = "Oprávnenia" +other = "Iné" +perPageInfo = "Informácie pre strany" +tableOfContents = "Obsah" + +[getPdfInfo.other] +attachments = "Prílohy" +embeddedFiles = "Vložené súbory" +javaScript = "JavaScript" +layers = "Vrstvy" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Veľkosť" +annotations = "Anotácie" +images = "Obrázky" +links = "Odkazy" +fonts = "Písma" +xobjects = "Počty XObjectov" +multimedia = "Multimédiá" + +[getPdfInfo.summary] +pages = "Strany" +fileSize = "Veľkosť súboru" +pdfVersion = "Verzia PDF" +language = "Jazyk" +title = "Súhrn PDF" +author = "Autor" +created = "Vytvorené" +modified = "Upravené" +permsAll = "Všetky oprávnenia povolené" +permsRestricted = "{{count}} obmedzení" +permsMixed = "Niektoré oprávnenia obmedzené" +hasCompliance = "Spĺňa štandardy súladu" +noCompliance = "Žiadne štandardy súladu" +basic = "Základné informácie" +documentInfo = "Informácie o dokumente" +securityTitle = "Stav zabezpečenia" +technical = "Technické" +overviewTitle = "Prehľad PDF" + +[getPdfInfo.summary.security] +encrypted = "Šifrované PDF - prítomná ochrana heslom" +unencrypted = "Nešifrované PDF - Bez ochrany heslom" + +[getPdfInfo.summary.tech] +images = "Obrázky" +fonts = "Písma" +formFields = "Polia formulára" +embeddedFiles = "Vložené súbory" +javaScript = "JavaScript" +layers = "Vrstvy" +bookmarks = "Záložky" +multimedia = "Multimédiá" + +[getPdfInfo.summary.overview] +untitled = "nepomenovaný dokument" +unknown = "Neznámy autor" +text = "Toto je {{pages}}-stranové PDF s názvom {{title}} od autora {{author}} (verzia PDF {{version}})." + +[getPdfInfo.error] +partial = "Niektoré súbory sa nepodarilo spracovať." +unexpected = "Počas extrahovania došlo k neočakávanej chybe." + +[getPdfInfo.status] +complete = "Extrahovanie dokončené" [extractPage] tags = "extrahovať" @@ -3438,6 +3539,9 @@ signinTitle = "Prosím, prihláste sa" ssoSignIn = "Prihlásiť sa cez Single Sign-on" oAuth2AutoCreateDisabled = "Vytváranie používateľa cez OAUTH2 je zakázané" oAuth2AdminBlockedUser = "Registrácia alebo prihlasovanie neregistrovaných používateľov je momentálne blokované. Kontaktujte administrátora." +oAuth2RequiresLicense = "Prihlásenie cez OAuth/SSO vyžaduje platenú licenciu (Server alebo Enterprise). Obráťte sa na administrátora, aby aktualizoval váš plán." +saml2RequiresLicense = "Prihlásenie cez SAML vyžaduje platenú licenciu (Server alebo Enterprise). Obráťte sa na administrátora, aby aktualizoval váš plán." +maxUsersReached = "Bol dosiahnutý maximálny počet používateľov pre vašu aktuálnu licenciu. Obráťte sa na administrátora, aby aktualizoval váš plán alebo pridal ďalšie miesta." oauth2RequestNotFound = "Požiadavka na autorizáciu sa nenašla" oauth2InvalidUserInfoResponse = "Neplatná odpoveď User Info" oauth2invalidRequest = "Neplatná požiadavka" @@ -3533,7 +3637,7 @@ title = "PDF na jednu stránku" header = "PDF na jednu stránku" submit = "Konvertovať na jednu stránku" description = "Tento nástroj zlúči všetky strany vášho PDF do jednej veľkej stránky. Šírka zostane rovnaká ako pri pôvodných stranách, výška bude súčtom výšok všetkých strán." -filenamePrefix = "single_page" +filenamePrefix = "jedna_strana" [pdfToSinglePage.files] placeholder = "Vyberte súbor PDF v hlavnom zobrazení, aby ste začali" @@ -3771,7 +3875,7 @@ version = "Aktuálne vydanie" title = "Dokumentácia API" header = "Dokumentácia API" desc = "Zobraziť a testovať API endpointy Stirling PDF" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,dokumentácia,swagger,endpointy,vývoj" [cookieBanner.popUp] title = "Ako používame súbory cookie" @@ -3846,14 +3950,17 @@ fitToWidth = "Prispôsobiť šírke" actualSize = "Skutočná veľkosť" [viewer] +cannotPreviewFile = "Nedá sa zobraziť náhľad súboru" +dualPageView = "Dvojstranové zobrazenie" firstPage = "Prvá strana" lastPage = "Posledná strana" -previousPage = "Predchádzajúca strana" nextPage = "Nasledujúca strana" +onlyPdfSupported = "Prehliadač podporuje iba súbory PDF. Tento súbor sa zdá byť iného formátu." +previousPage = "Predchádzajúca strana" +singlePageView = "Zobrazenie jednej strany" +unknownFile = "Neznámy súbor" zoomIn = "Priblížiť" zoomOut = "Oddialiť" -singlePageView = "Zobrazenie jednej strany" -dualPageView = "Dvojstranové zobrazenie" [rightRail] closeSelected = "Zavrieť vybrané súbory" @@ -3877,6 +3984,7 @@ toggleSidebar = "Prepnúť bočný panel" exportSelected = "Exportovať vybrané strany" toggleAnnotations = "Prepnúť zobrazenie anotácií" annotationMode = "Prepnúť režim anotácií" +print = "Vytlačiť PDF" draw = "Kresliť" save = "Uložiť" saveChanges = "Uložiť zmeny" @@ -4487,13 +4595,14 @@ label = "Zásady používania súborov cookie" description = "URL alebo názov súboru so zásadami používania súborov cookie" [admin.settings.legal.impressum] -label = "Impressum" +label = "Impresum" description = "URL alebo názov súboru k Impressu (požadované v niektorých jurisdikciách)" [admin.settings.premium] title = "Premium a Enterprise" description = "Nakonfigurujte svoj Premium alebo Enterprise licenčný kľúč." license = "Konfigurácia licencie" +noInput = "Zadajte licenčný kľúč alebo súbor" [admin.settings.premium.licenseKey] toggle = "Máte licenčný kľúč alebo súbor certifikátu?" @@ -4511,6 +4620,25 @@ line1 = "Prepísanie aktuálneho licenčného kľúča nemožno vrátiť späť. line2 = "Vaša predchádzajúca licencia bude natrvalo stratená, pokiaľ ju nemáte zálohovanú inde." line3 = "Dôležité: Licenčné kľúče uchovávajte súkromné a v bezpečí. Nikdy ich nezdieľajte verejne." +[admin.settings.premium.inputMethod] +text = "Licenčný kľúč" +file = "Súbor certifikátu" + +[admin.settings.premium.file] +label = "Súbor licenčného certifikátu" +description = "Nahrajte svoj licenčný súbor .lic alebo .cert z offline nákupu" +choose = "Vybrať licenčný súbor" +selected = "Vybrané: {{filename}} ({{size}})" +successMessage = "Licenčný súbor bol úspešne nahraný a aktivovaný. Reštart nie je potrebný." + +[admin.settings.premium.currentLicense] +title = "Aktívna licencia" +file = "Zdroj: Licenčný súbor ({{path}})" +key = "Zdroj: Licenčný kľúč" +type = "Typ: {{type}}" +noInput = "Zadajte licenčný kľúč alebo nahrajte súbor certifikátu" +success = "Úspech" + [admin.settings.premium.enabled] label = "Povoliť Premium funkcie" description = "Povoliť kontrolu licenčného kľúča pre pro/enterprise funkcie" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} vybraných" download = "Stiahnuť" delete = "Vymazať" unsupported = "Nepodporované" +active = "Aktívne" addToUpload = "Pridať na nahratie" +closeFile = "Zatvoriť súbor" deleteAll = "Odstrániť všetko" loadingFiles = "Načítavajú sa súbory..." noFiles = "Nie sú dostupné žiadne súbory" @@ -5132,7 +5262,7 @@ upgrade = "Upgradovať teraz →" freeTitle = "Serverová licencia" overLimitTitle = "Potrebná serverová licencia" overLimitBody = "Naše licencovanie povoľuje až {{freeTierLimit}} používateľov zdarma na server. Máte {{overLimitUserCopy}} používateľov Stirling. Ak chcete pokračovať bez prerušenia, prejdite na plán Stirling Server - neobmedzené miesta, úpravy textu PDF a plná správa pre $99/server/mo." -freeBody = "Naše licencovanie Open-Core povoľuje až {{freeTierLimit}} používateľov zdarma na server. Ak chcete škálovať bez prerušenia a získať skorý prístup k nášmu novému nástroju na úpravu textu PDF, odporúčame plán Stirling Server - plné úpravy a neobmedzené miesta za $99/server/mo." +freeBody = "Naše licencovanie Open-Core umožňuje až {{freeTierLimit}} používateľov zadarmo na server. Na plynulé škálovanie odporúčame plán Stirling Server - neobmedzený počet používateľov a podporu SSO za $99/server/mes." [onboarding.desktopInstall] title = "Stiahnuť" @@ -5178,7 +5308,7 @@ active = "Aktívny" disabled = "Zakázaný" activeSession = "Aktívna relácia" member = "Člen" -admin = "Admin" +admin = "Administrátor" editRole = "Upraviť rolu" enable = "Povoliť" disable = "Zakázať" @@ -5237,6 +5367,31 @@ error = "Nepodarilo sa aktualizovať stav používateľa" success = "Používateľ bol úspešne odstránený" error = "Nepodarilo sa odstrániť používateľa" +[workspace.people.changePassword] +action = "Zmeniť heslo" +title = "Zmeniť heslo" +subtitle = "Aktualizovať heslo pre" +newPassword = "Nové heslo" +confirmPassword = "Potvrdiť heslo" +placeholder = "Zadajte nové heslo" +confirmPlaceholder = "Zadajte nové heslo ešte raz" +passwordRequired = "Zadajte nové heslo" +passwordMismatch = "Heslá sa nezhodujú" +generateRandom = "Vygenerovať bezpečné heslo" +generatedPreview = "Vygenerované heslo:" +copyTooltip = "Kopírovať do schránky" +copiedToClipboard = "Heslo skopírované do schránky" +copyFailed = "Heslo sa nepodarilo skopírovať" +sendEmail = "Odoslať používateľovi e-mail o tejto zmene" +includePassword = "Zahrnúť nové heslo do e-mailu" +forcePasswordChange = "Vynútiť zmenu hesla pri najbližšom prihlásení" +emailUnavailable = "E-mailová adresa tohto používateľa nie je platná. Upozornenia sú vypnuté." +smtpDisabled = "E-mailové upozornenia vyžadujú, aby bolo v nastaveniach povolené SMTP." +notifyOnly = "Odošle sa e-mail bez hesla, ktorý používateľa informuje, že ho zmenil administrátor." +submit = "Aktualizovať heslo" +success = "Heslo bolo úspešne aktualizované" +error = "Heslo sa nepodarilo aktualizovať" + [workspace.people.emailInvite] tab = "Pozvánka e-mailom" description = "Nižšie napíšte alebo vložte e-mailové adresy oddelené čiarkami. Používatelia dostanú prihlasovacie údaje e-mailom." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Vyžaduje sa aspoň jedna e-mailová adresa" submit = "Odoslať pozvánky" success = "Používatelia boli úspešne pozvaní" -partialSuccess = "Niektoré pozvánky zlyhali" +partialFailure = "Niektoré pozvánky zlyhali" allFailed = "Nepodarilo sa pozvať používateľov" error = "Nepodarilo sa odoslať pozvánky" @@ -5770,6 +5925,7 @@ subtitle = "Prihláste sa pomocou svojho účtu Stirling" [setup.selfhosted] title = "Prihlásiť sa na server" subtitle = "Zadajte prihlasovacie údaje k serveru" +link = "alebo sa pripojte k self-hosted účtu" [setup.server] title = "Pripojiť sa na server" @@ -5788,6 +5944,14 @@ description = "Zadajte úplnú URL svojho self-hostovaného servera Stirling PDF emptyUrl = "Zadajte URL servera" unreachable = "Nedá sa pripojiť k serveru" testFailed = "Test pripojenia zlyhal" +configFetch = "Nepodarilo sa načítať konfiguráciu servera. Skontrolujte URL a skúste to znova." + +[setup.server.error.securityDisabled] +title = "Prihlásenie nie je povolené" +body = "Na tomto serveri nie je povolené prihlásenie. Ak sa chcete pripojiť k tomuto serveru, musíte povoliť overenie:" +step1 = "Nastavte DOCKER_ENABLE_SECURITY=true vo svojom prostredí" +step2 = "Alebo nastavte security.enableLogin=true v súbore settings.yml" +step3 = "Reštartujte server" [setup.login] title = "Prihlásiť sa" @@ -5797,13 +5961,20 @@ submit = "Prihlásiť sa" signInWith = "Prihlásiť sa cez" oauthPending = "Otvára sa prehliadač na overenie..." orContinueWith = "Alebo pokračujte emailom" +serverRequirement = "Poznámka: Na serveri musí byť povolené prihlásenie." +showInstructions = "Ako povoliť?" +hideInstructions = "Skryť pokyny" +instructions = "Na povolenie prihlásenia na vašom serveri Stirling PDF:" +instructionsEnvVar = "Nastavte premennú prostredia:" +instructionsOrYml = "Alebo v súbore settings.yml:" +instructionsRestart = "Potom reštartujte server, aby sa zmeny prejavili." [setup.login.username] label = "Používateľské meno" placeholder = "Zadajte používateľské meno" [setup.login.email] -label = "Email" +label = "E-mail" placeholder = "Zadajte svoj email" [setup.login.password] @@ -5840,7 +6011,7 @@ paragraph = "Strana s odsekmi" sparse = "Riedky text" [pdfTextEditor.groupingMode] -auto = "Auto" +auto = "Automaticky" paragraph = "Odsek" singleLine = "Jeden riadok" @@ -5853,6 +6024,7 @@ earlyAccess = "Skorý prístup" reset = "Resetovať zmeny" downloadJson = "Stiahnuť JSON" generatePdf = "Vygenerovať PDF" +saveChanges = "Uložiť zmeny" [pdfTextEditor.options.autoScaleText] title = "Automaticky prispôsobiť text do boxov" @@ -5890,6 +6062,8 @@ alpha = "Tento alfa prehliadač sa stále vyvíja—niektoré písma, farby, efe [pdfTextEditor.empty] title = "Žiadny dokument nie je načítaný" subtitle = "Načítajte súbor PDF alebo JSON a začnite upravovať textový obsah." +dropzone = "Pretiahnite sem súbor PDF alebo JSON, alebo kliknite pre prehľadanie" +dropzoneWithFiles = "Vyberte súbor na karte Súbory, alebo sem presuňte súbor PDF alebo JSON, prípadne kliknite pre prehľadanie" [pdfTextEditor.welcomeBanner] title = "Vitajte v PDF Text Editore (Early Access)" diff --git a/frontend/public/locales/sl-SI/translation.toml b/frontend/public/locales/sl-SI/translation.toml index 36497b6a9..4d80264fb 100644 --- a/frontend/public/locales/sl-SI/translation.toml +++ b/frontend/public/locales/sl-SI/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Odstrani iz priljubljenih" fullscreen = "Preklopi na celozaslonski način" sidebar = "Preklopi na način stranske vrstice" +[backendStartup] +notFoundTitle = "Zaledje ni najdeno" +retry = "Poskusi znova" +unreachable = "Aplikacija se trenutno ne more povezati z zaledjem. Preverite stanje zaledja in omrežno povezavo, nato poskusite znova." + [zipWarning] title = "Velika datoteka ZIP" message = "Ta ZIP vsebuje {{count}} datotek. Vseeno razpakiram?" @@ -912,6 +917,9 @@ desc = "Sestavite večkorakovne poteke z veriženjem dejanj PDF. Idealno za pona desc = "Prekriva PDF-je na vrhu drugega PDF-ja" title = "Prekrivanje PDF-jev" +[home.pdfTextEditor] +title = "Urejevalnik besedila PDF" +desc = "Urejajte obstoječe besedilo in slike v PDF-jih" [home.addText] tags = "besedilo,pripomba,oznaka" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Narisan podpis" defaultImageLabel = "Naložen podpis" defaultTextLabel = "Vpisan podpis" saveButton = "Shrani podpis" +savePersonal = "Shrani osebno" +saveShared = "Shrani deljeno" saveUnavailable = "Najprej ustvarite podpis, da ga lahko shranite." noChanges = "Trenutni podpis je že shranjen." +tempStorageTitle = "Začasno shranjevanje v brskalniku" +tempStorageDescription = "Podpisi so shranjeni samo v vašem brskalniku. Izgubite jih, če počistite podatke brskalnika ali zamenjate brskalnik." +personalHeading = "Osebni podpisi" +sharedHeading = "Deljeni podpisi" +personalDescription = "Te podpise vidite samo vi." +sharedDescription = "Vsi uporabniki lahko te podpise vidijo in uporabljajo." [sign.saved.type] canvas = "Risba" @@ -3020,6 +3036,91 @@ title = "Pridobite informacije o PDF-ju" header = "Pridobite informacije o PDF-ju" submit = "Pridobi informacije" downloadJson = "Prenesite JSON" +processing = "Pridobivanje informacij..." +results = "Rezultati" +noResults = "Za ustvarjanje poročila zaženite orodje." +downloads = "Prenosi" +noneDetected = "Ni zaznanih" +indexTitle = "Kazalo" + +[getPdfInfo.report] +entryLabel = "Celoviti povzetek informacij" +shortTitle = "Informacije o PDF" + +[getPdfInfo.sections] +metadata = "Metapodatki" +formFields = "Polja obrazca" +basicInfo = "Osnovne informacije" +documentInfo = "Informacije o dokumentu" +compliance = "Skladnost" +encryption = "Šifriranje" +permissions = "Dovoljenja" +other = "Drugo" +perPageInfo = "Informacije po strani" +tableOfContents = "Kazalo vsebine" + +[getPdfInfo.other] +attachments = "Priloge" +embeddedFiles = "Vdelane datoteke" +javaScript = "JavaScript" +layers = "Plasti" +structureTree = "Drevo strukture" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Velikost" +annotations = "Opombe" +images = "Slike" +links = "Povezave" +fonts = "Pisave" +xobjects = "Število XObject" +multimedia = "Večpredstavnost" + +[getPdfInfo.summary] +pages = "Strani" +fileSize = "Velikost datoteke" +pdfVersion = "Različica PDF" +language = "Jezik" +title = "Povzetek PDF" +author = "Avtor" +created = "Ustvarjeno" +modified = "Spremenjeno" +permsAll = "Vsa dovoljenja so dovoljena" +permsRestricted = "{{count}} omejitev" +permsMixed = "Nekatera dovoljenja omejena" +hasCompliance = "Ima standarde skladnosti" +noCompliance = "Brez standardov skladnosti" +basic = "Osnovne informacije" +documentInfo = "Informacije o dokumentu" +securityTitle = "Varnostno stanje" +technical = "Tehnično" +overviewTitle = "Pregled PDF" + +[getPdfInfo.summary.security] +encrypted = "Šifriran PDF - prisotna zaščita z geslom" +unencrypted = "Nešifriran PDF - brez zaščite z geslom" + +[getPdfInfo.summary.tech] +images = "Slike" +fonts = "Pisave" +formFields = "Polja obrazca" +embeddedFiles = "Vdelane datoteke" +javaScript = "JavaScript" +layers = "Plasti" +bookmarks = "Zaznamki" +multimedia = "Večpredstavnost" + +[getPdfInfo.summary.overview] +untitled = "neimenovan dokument" +unknown = "Neznani avtor" +text = "To je {{pages}}-stranski PDF z naslovom {{title}}, ki ga je ustvaril {{author}} (različica PDF {{version}})." + +[getPdfInfo.error] +partial = "Nekaterih datotek ni bilo mogoče obdelati." +unexpected = "Nepričakovana napaka med pridobivanjem." + +[getPdfInfo.status] +complete = "Pridobivanje zaključeno" [extractPage] tags = "izvleček" @@ -3438,6 +3539,9 @@ signinTitle = "Prosim prijavite se" ssoSignIn = "Prijava prek enotne prijave" oAuth2AutoCreateDisabled = "OAUTH2 Samodejno ustvarjanje uporabnika onemogočeno" oAuth2AdminBlockedUser = "Registracija ali prijava neregistriranih uporabnikov je trenutno blokirana. Prosimo kontaktirajte skrbnika." +oAuth2RequiresLicense = "Prijava prek OAuth/SSO zahteva plačljivo licenco (Server ali Enterprise). Obrnite se na skrbnika, da nadgradi vaš načrt." +saml2RequiresLicense = "Prijava prek SAML zahteva plačljivo licenco (Server ali Enterprise). Obrnite se na skrbnika, da nadgradi vaš načrt." +maxUsersReached = "Doseženo je največje število uporabnikov za vašo trenutno licenco. Obrnite se na skrbnika, da nadgradi vaš načrt ali doda več mest." oauth2RequestNotFound = "Zahteva za avtorizacijo ni bila najdena" oauth2InvalidUserInfoResponse = "Neveljaven odgovor z informacijami o uporabniku" oauth2invalidRequest = "Neveljavna zahteva" @@ -3846,14 +3950,17 @@ fitToWidth = "Prilagodi širini" actualSize = "Dejanska velikost" [viewer] +cannotPreviewFile = "Predogled datoteke ni mogoč" +dualPageView = "Dvo-stranski pogled" firstPage = "Prva stran" lastPage = "Zadnja stran" -previousPage = "Prejšnja stran" nextPage = "Naslednja stran" +onlyPdfSupported = "Pregledovalnik podpira samo PDF datoteke. Ta datoteka je videti v drugačnem formatu." +previousPage = "Prejšnja stran" +singlePageView = "Enostranski pogled" +unknownFile = "Neznana datoteka" zoomIn = "Povečaj" zoomOut = "Pomanjšaj" -singlePageView = "Enostranski pogled" -dualPageView = "Dvo-stranski pogled" [rightRail] closeSelected = "Zapri izbrane datoteke" @@ -3877,6 +3984,7 @@ toggleSidebar = "Preklopi stransko vrstico" exportSelected = "Izvozi izbrane strani" toggleAnnotations = "Preklopi vidnost opomb" annotationMode = "Preklopi način opomb" +print = "Natisni PDF" draw = "Riši" save = "Shrani" saveChanges = "Shrani spremembe" @@ -4494,6 +4602,7 @@ description = "URL ali ime datoteke do impressuma (zahtevano v nekaterih jurisdi title = "Premium in Enterprise" description = "Konfigurirajte svoj ključ licence Premium ali Enterprise." license = "Konfiguracija licence" +noInput = "Navedite licenčni ključ ali datoteko" [admin.settings.premium.licenseKey] toggle = "Imate licenčni ključ ali potrdilno datoteko?" @@ -4511,6 +4620,25 @@ line1 = "Prepis trenutnega licenčnega ključa ni mogoče razveljaviti." line2 = "Prejšnja licenca bo trajno izgubljena, razen če ste jo varnostno kopirali drugje." line3 = "Pomembno: Licenčne ključe hranite zasebno in varno. Nikoli jih ne delite javno." +[admin.settings.premium.inputMethod] +text = "Licenčni ključ" +file = "Datoteka potrdila" + +[admin.settings.premium.file] +label = "Datoteka licenčnega potrdila" +description = "Naložite svojo licenčno datoteko .lic ali .cert iz nakupov brez povezave" +choose = "Izberite licenčno datoteko" +selected = "Izbrano: {{filename}} ({{size}})" +successMessage = "Licenčna datoteka je bila uspešno naložena in aktivirana. Ponovni zagon ni potreben." + +[admin.settings.premium.currentLicense] +title = "Aktivna licenca" +file = "Vir: licenčna datoteka ({{path}})" +key = "Vir: licenčni ključ" +type = "Vrsta: {{type}}" +noInput = "Navedite licenčni ključ ali naložite datoteko potrdila" +success = "Uspešno" + [admin.settings.premium.enabled] label = "Omogoči funkcije Premium" description = "Omogoči preverjanje licenčnega ključa za funkcije pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} izbranih" download = "Prenos" delete = "Izbriši" unsupported = "Nepodprto" +active = "Aktivno" addToUpload = "Dodaj k nalaganju" +closeFile = "Zapri datoteko" deleteAll = "Izbriši vse" loadingFiles = "Nalaganje datotek..." noFiles = "Ni razpoložljivih datotek" @@ -5132,7 +5262,7 @@ upgrade = "Nadgradi zdaj →" freeTitle = "Licenca strežnika" overLimitTitle = "Potrebna licenca strežnika" overLimitBody = "Naše licenciranje brezplačno omogoča do {{freeTierLimit}} uporabnikov na strežnik. Imate {{overLimitUserCopy}} uporabnikov Stirling. Za nemoteno uporabo nadgradite na načrt Stirling Server – neomejena mesta, urejanje besedila PDF in popoln skrbniški nadzor za $99/strežnik/mesec." -freeBody = "Naše licenciranje Open-Core brezplačno omogoča do {{freeTierLimit}} uporabnikov na strežnik. Za nemoteno rast in zgodnji dostop do našega novega orodja za urejanje besedila PDF priporočamo načrt Stirling Server – polno urejanje in neomejena mesta za $99/strežnik/mesec." +freeBody = "Naše licenciranje Open-Core omogoča do {{freeTierLimit}} uporabnikov brezplačno na strežnik. Za nemoteno skaliranje priporočamo načrt Stirling Server - neomejena mesta in podpora za SSO za $99/strežnik/mesec." [onboarding.desktopInstall] title = "Prenesi" @@ -5237,6 +5367,31 @@ error = "Stanja uporabnika ni bilo mogoče posodobiti" success = "Uporabnik uspešno izbrisan" error = "Uporabnika ni bilo mogoče izbrisati" +[workspace.people.changePassword] +action = "Spremeni geslo" +title = "Spremeni geslo" +subtitle = "Posodobite geslo za" +newPassword = "Novo geslo" +confirmPassword = "Potrdi geslo" +placeholder = "Vnesite novo geslo" +confirmPlaceholder = "Znova vnesite novo geslo" +passwordRequired = "Prosimo, vnesite novo geslo" +passwordMismatch = "Gesli se ne ujemata" +generateRandom = "Ustvari varno geslo" +generatedPreview = "Ustvarjeno geslo:" +copyTooltip = "Kopiraj v odložišče" +copiedToClipboard = "Geslo je kopirano v odložišče" +copyFailed = "Kopiranje gesla ni uspelo" +sendEmail = "Uporabniku pošlji e-pošto o tej spremembi" +includePassword = "V e-pošto vključi novo geslo" +forcePasswordChange = "Prisili uporabnika, da ob naslednji prijavi spremeni geslo" +emailUnavailable = "E-poštni naslov tega uporabnika ni veljaven. Obvestila so onemogočena." +smtpDisabled = "Za e-poštna obvestila mora biti v nastavitvah omogočen SMTP." +notifyOnly = "Poslano bo e-poštno sporočilo brez gesla, ki bo uporabnika obvestilo, da ga je skrbnik spremenil." +submit = "Posodobi geslo" +success = "Geslo je bilo uspešno posodobljeno" +error = "Posodobitev gesla ni uspela" + [workspace.people.emailInvite] tab = "E-poštno povabilo" description = "Spodaj vnesite ali prilepite e-poštne naslove, ločene z vejicami. Uporabniki bodo prejeli prijavne podatke po e-pošti." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Zahtevan je vsaj en e-poštni naslov" submit = "Pošlji povabila" success = "uporabnik(i) uspešno povabljen(i)" -partialSuccess = "Nekatera povabila niso uspela" +partialFailure = "Nekatera povabila niso uspela" allFailed = "Uporabnikov ni bilo mogoče povabiti" error = "Pošiljanje povabil ni uspelo" @@ -5770,6 +5925,7 @@ subtitle = "Prijavite se s svojim računom Stirling" [setup.selfhosted] title = "Prijavite se v strežnik" subtitle = "Vnesite poverilnice strežnika" +link = "ali se povežite z računom na lastnem strežniku" [setup.server] title = "Poveži se s strežnikom" @@ -5788,6 +5944,14 @@ description = "Vnesite celoten URL svojega samogostovanega strežnika Stirling P emptyUrl = "Vnesite URL strežnika" unreachable = "Povezava s strežnikom ni uspela" testFailed = "Preizkus povezave ni uspel" +configFetch = "Pridobitev konfiguracije strežnika ni uspela. Preverite URL in poskusite znova." + +[setup.server.error.securityDisabled] +title = "Prijava ni omogočena" +body = "Na tem strežniku prijava ni omogočena. Za povezavo s tem strežnikom morate omogočiti overjanje:" +step1 = "V svojem okolju nastavite DOCKER_ENABLE_SECURITY=true" +step2 = "Ali nastavite security.enableLogin=true v settings.yml" +step3 = "Znova zaženite strežnik" [setup.login] title = "Prijava" @@ -5797,6 +5961,13 @@ submit = "Prijava" signInWith = "Prijavite se z" oauthPending = "Odpiranje brskalnika za overjanje..." orContinueWith = "Ali nadaljujte z e-pošto" +serverRequirement = "Opomba: Strežnik mora imeti omogočeno prijavo." +showInstructions = "Kako omogočiti?" +hideInstructions = "Skrij navodila" +instructions = "Za omogočanje prijave na vašem strežniku Stirling PDF:" +instructionsEnvVar = "Nastavite okoljsko spremenljivko:" +instructionsOrYml = "Ali v settings.yml:" +instructionsRestart = "Nato znova zaženite strežnik, da spremembe začnejo veljati." [setup.login.username] label = "Uporabniško ime" @@ -5853,6 +6024,7 @@ earlyAccess = "Zgodnji dostop" reset = "Ponastavi spremembe" downloadJson = "Prenesi JSON" generatePdf = "Ustvari PDF" +saveChanges = "Shrani spremembe" [pdfTextEditor.options.autoScaleText] title = "Samodejno prilagodi besedilo okvirjem" @@ -5890,6 +6062,8 @@ alpha = "Ta alfa pregledovalnik se še razvija — nekatere pisave, barve, učin [pdfTextEditor.empty] title = "Ni naloženega dokumenta" subtitle = "Naložite datoteko PDF ali JSON, da začnete urejati besedilo." +dropzone = "Sem povlecite in spustite datoteko PDF ali JSON ali kliknite za brskanje" +dropzoneWithFiles = "Izberite datoteko na zavihku Datoteke ali sem povlecite in spustite datoteko PDF ali JSON oziroma kliknite za brskanje" [pdfTextEditor.welcomeBanner] title = "Dobrodošli v PDF Text Editor (zgodnji dostop)" diff --git a/frontend/public/locales/sr-LATN-RS/translation.toml b/frontend/public/locales/sr-LATN-RS/translation.toml index a24113123..9404bc001 100644 --- a/frontend/public/locales/sr-LATN-RS/translation.toml +++ b/frontend/public/locales/sr-LATN-RS/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Ukloni iz omiljenog" fullscreen = "Prebaci na režim celog ekrana" sidebar = "Prebaci na režim bočne trake" +[backendStartup] +notFoundTitle = "Bekend nije pronađen" +retry = "Pokušaj ponovo" +unreachable = "Aplikacija trenutno ne može da se poveže sa bekendom. Proverite status bekenda i mrežnu povezanost, pa pokušajte ponovo." + [zipWarning] title = "Velika ZIP datoteka" message = "Ovaj ZIP sadrži {{count}} datoteka. Ipak raspakovati?" @@ -912,6 +917,9 @@ desc = "Gradite višekorake tokove rada povezivanjem PDF akcija. Idealno za pona desc = "Preklapa PDF-ove jedan preko drugog" title = "Preklapanje PDF-ova" +[home.pdfTextEditor] +title = "PDF uređivač teksta" +desc = "Uređujte postojeći tekst i slike unutar PDF-ova" [home.addText] tags = "tekst,anotacija,oznaka" @@ -1213,11 +1221,11 @@ pdfaDigitalSignatureWarning = "PDF sadrži digitalni potpis. Biće uklonjen u sl fileFormat = "Format datoteke" wordDoc = "Word dokument" wordDocExt = "Word dokument (.docx)" -odtExt = "OpenDocument Text (.odt)" +odtExt = "OpenDocument tekst (.odt)" pptExt = "PowerPoint (.pptx)" -odpExt = "OpenDocument Presentation (.odp)" +odpExt = "OpenDocument prezentacija (.odp)" txtExt = "Običan tekst (.txt)" -rtfExt = "Rich Text Format (.rtf)" +rtfExt = "Format obogaćenog teksta (.rtf)" selectedFiles = "Izabrane datoteke" noFileSelected = "Nije izabrana nijedna datoteka. Koristite panel datoteka da dodate datoteke." convertFiles = "Konvertuj datoteke" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Crtani potpis" defaultImageLabel = "Otpremljeni potpis" defaultTextLabel = "Ukucani potpis" saveButton = "Sačuvaj potpis" +savePersonal = "Sačuvaj lično" +saveShared = "Sačuvaj deljeno" saveUnavailable = "Prvo napravite potpis da biste ga sačuvali." noChanges = "Trenutni potpis je već sačuvan." +tempStorageTitle = "Privremeno skladište pregledača" +tempStorageDescription = "Potpisi se čuvaju samo u vašem pregledaču. Biće izgubljeni ako obrišete podatke pregledača ili promenite pregledač." +personalHeading = "Lični potpisi" +sharedHeading = "Deljeni potpisi" +personalDescription = "Samo vi možete da vidite ove potpise." +sharedDescription = "Svi korisnici mogu da vide i koriste ove potpise." [sign.saved.type] canvas = "Crtanje" @@ -2318,7 +2334,7 @@ title = "Ravnanje" header = "Ravnanje PDF fajlova" flattenOnlyForms = "Izravnaj samo forme" submit = "Ravnanje" -filenamePrefix = "flattened" +filenamePrefix = "spljošteno" [flatten.files] placeholder = "Izaberite PDF datoteku u glavnom prikazu da biste započeli" @@ -3020,6 +3036,91 @@ title = "Informacije o PDF-u" header = "Informacije o PDF-u" submit = "Informacije" downloadJson = "Preuzmi JSON" +processing = "Izdvajanje informacija..." +results = "Rezultati" +noResults = "Pokrenite alat da generišete izveštaj." +downloads = "Preuzimanja" +noneDetected = "Nije otkriveno ništa" +indexTitle = "Indeks" + +[getPdfInfo.report] +entryLabel = "Potpuni sažetak informacija" +shortTitle = "Informacije o PDF-u" + +[getPdfInfo.sections] +metadata = "Metapodaci" +formFields = "Polja obrasca" +basicInfo = "Osnovne informacije" +documentInfo = "Informacije o dokumentu" +compliance = "Usklađenost" +encryption = "Šifrovanje" +permissions = "Dozvole" +other = "Ostalo" +perPageInfo = "Informacije po stranici" +tableOfContents = "Sadržaj" + +[getPdfInfo.other] +attachments = "Prilozi" +embeddedFiles = "Ugrađene datoteke" +javaScript = "JavaScript" +layers = "Slojevi" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Veličina" +annotations = "Anotacije" +images = "Slike" +links = "Linkovi" +fonts = "Fontovi" +xobjects = "Broj XObject-ova" +multimedia = "Multimedija" + +[getPdfInfo.summary] +pages = "Stranice" +fileSize = "Veličina datoteke" +pdfVersion = "PDF verzija" +language = "Jezik" +title = "PDF sažetak" +author = "Autor" +created = "Kreirano" +modified = "Izmenjeno" +permsAll = "Sve dozvole su omogućene" +permsRestricted = "{{count}} ograničenja" +permsMixed = "Neke dozvole su ograničene" +hasCompliance = "Ima standarde usklađenosti" +noCompliance = "Nema standarda usklađenosti" +basic = "Osnovne informacije" +documentInfo = "Informacije o dokumentu" +securityTitle = "Status bezbednosti" +technical = "Tehničko" +overviewTitle = "Pregled PDF-a" + +[getPdfInfo.summary.security] +encrypted = "Šifrovan PDF - prisutna zaštita lozinkom" +unencrypted = "Nešifrovan PDF - bez zaštite lozinkom" + +[getPdfInfo.summary.tech] +images = "Slike" +fonts = "Fontovi" +formFields = "Polja obrasca" +embeddedFiles = "Ugrađene datoteke" +javaScript = "JavaScript" +layers = "Slojevi" +bookmarks = "Obeleživači" +multimedia = "Multimedija" + +[getPdfInfo.summary.overview] +untitled = "neimenovani dokument" +unknown = "Nepoznat autor" +text = "Ovo je PDF od {{pages}} stranica pod nazivom {{title}} koji je kreirao {{author}} (PDF verzija {{version}})." + +[getPdfInfo.error] +partial = "Neke datoteke nije bilo moguće obraditi." +unexpected = "Neočekivana greška tokom izdvajanja." + +[getPdfInfo.status] +complete = "Izdvajanje završeno" [extractPage] tags = "izdvajanje" @@ -3438,6 +3539,9 @@ signinTitle = "Molimo vas da se prijavite" ssoSignIn = "Prijavite se putem jedinstvene prijave" oAuth2AutoCreateDisabled = "OAUTH2 automatsko kreiranje korisnika je onemogućeno" oAuth2AdminBlockedUser = "Registracija ili prijava neregistrovanog korisnika je trenutno onemogućeno. Kontaktirajte administratora." +oAuth2RequiresLicense = "Prijava putem OAuth/SSO zahteva plaćenu licencu (Server ili Enterprise). Kontaktirajte administratora da unapredi vaš plan." +saml2RequiresLicense = "Prijava putem SAML zahteva plaćenu licencu (Server ili Enterprise). Kontaktirajte administratora da unapredi vaš plan." +maxUsersReached = "Dostignut je maksimalni broj korisnika za vašu trenutnu licencu. Kontaktirajte administratora da unapredi vaš plan ili doda više mesta." oauth2RequestNotFound = "Zahtev za autorizaciju nije pronađen" oauth2InvalidUserInfoResponse = "Neispravan odgovor sa korisničkim informacijama" oauth2invalidRequest = "Neispravan zahtev" @@ -3771,7 +3875,7 @@ version = "Aktuelno izdanje" title = "API dokumentacija" header = "API dokumentacija" desc = "Pregledajte i testirajte Stirling PDF API endpoints" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,dokumentacija,swagger,krajnje tačke,razvoj" [cookieBanner.popUp] title = "Kako koristimo kolačiće" @@ -3846,14 +3950,17 @@ fitToWidth = "Uklopi po širini" actualSize = "Stvarna veličina" [viewer] +cannotPreviewFile = "Nije moguće pregledati datoteku" +dualPageView = "Prikaz dve stranice" firstPage = "Prva stranica" lastPage = "Poslednja stranica" -previousPage = "Prethodna stranica" nextPage = "Sledeća stranica" +onlyPdfSupported = "Prikazivač podržava samo PDF datoteke. Izgleda da je ova datoteka drugačijeg formata." +previousPage = "Prethodna stranica" +singlePageView = "Prikaz jedne stranice" +unknownFile = "Nepoznata datoteka" zoomIn = "Uvećaj" zoomOut = "Umanji" -singlePageView = "Prikaz jedne stranice" -dualPageView = "Prikaz dve stranice" [rightRail] closeSelected = "Zatvori izabrane fajlove" @@ -3877,6 +3984,7 @@ toggleSidebar = "Uključi/isključi bočnu traku" exportSelected = "Izvezi izabrane stranice" toggleAnnotations = "Uključi/isključi vidljivost anotacija" annotationMode = "Uključi/isključi režim anotacija" +print = "Štampaj PDF" draw = "Crtaj" save = "Sačuvaj" saveChanges = "Sačuvaj izmene" @@ -4494,6 +4602,7 @@ description = "URL ili naziv datoteke do impresuma (obavezno u nekim jurisdikcij title = "Premium i Enterprise" description = "Podesite svoj premium ili enterprise licencni ključ." license = "Konfiguracija licence" +noInput = "Navedite licencni ključ ili fajl" [admin.settings.premium.licenseKey] toggle = "Imate licencni ključ ili datoteku sertifikata?" @@ -4511,6 +4620,25 @@ line1 = "Prepisivanje vašeg trenutnog licencnog ključa ne može se opozvati." line2 = "Prethodna licenca će biti trajno izgubljena osim ako je niste sačuvali na drugom mestu." line3 = "Važno: Čuvajte licencne ključeve privatnim i bezbednim. Nikada ih javno ne delite." +[admin.settings.premium.inputMethod] +text = "Licencni ključ" +file = "Fajl sertifikata" + +[admin.settings.premium.file] +label = "Fajl licencnog sertifikata" +description = "Otpremite svoj .lic ili .cert licencni fajl iz offline kupovina" +choose = "Izaberite licencni fajl" +selected = "Izabrano: {{filename}} ({{size}})" +successMessage = "Licencni fajl je uspešno otpremljen i aktiviran. Restart nije potreban." + +[admin.settings.premium.currentLicense] +title = "Aktivna licenca" +file = "Izvor: licencni fajl ({{path}})" +key = "Izvor: licencni ključ" +type = "Tip: {{type}}" +noInput = "Navedite licencni ključ ili otpremite fajl sertifikata" +success = "Uspeh" + [admin.settings.premium.enabled] label = "Omogući premium funkcije" description = "Omogući provere licencnog ključa za pro/enterprise funkcije" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} izabrano" download = "Preuzmi" delete = "Obriši" unsupported = "Nepodržano" +active = "Aktivno" addToUpload = "Dodaj za otpremanje" +closeFile = "Zatvori datoteku" deleteAll = "Obriši sve" loadingFiles = "Učitavanje datoteka..." noFiles = "Nema dostupnih datoteka" @@ -5132,7 +5262,7 @@ upgrade = "Nadogradite sada →" freeTitle = "Serverska licenca" overLimitTitle = "Potrebna serverska licenca" overLimitBody = "Naše licenciranje dozvoljava do {{freeTierLimit}} korisnika besplatno po serveru. Imate {{overLimitUserCopy}} Stirling korisnika. Da nastavite bez prekida, pređite na Stirling Server plan - neograničena mesta, uređivanje PDF teksta i puna admin kontrola za $99/server/mes." -freeBody = "Naše Open-Core licenciranje dozvoljava do {{freeTierLimit}} korisnika besplatno po serveru. Da se bez prekida skalirate i dobijete rani pristup našem novom alatu za uređivanje PDF teksta, preporučujemo Stirling Server plan - puno uređivanje i neograničena mesta za $99/server/mes." +freeBody = "Naše licenciranje Open-Core dozvoljava do {{freeTierLimit}} korisnika besplatno po serveru. Za neometano skaliranje, preporučujemo plan Stirling Server - neograničena mesta i SSO podrška za $99/server/mo." [onboarding.desktopInstall] title = "Preuzmi" @@ -5237,6 +5367,31 @@ error = "Ažuriranje statusa korisnika nije uspelo" success = "Korisnik uspešno obrisan" error = "Brisanje korisnika nije uspelo" +[workspace.people.changePassword] +action = "Promenite lozinku" +title = "Promenite lozinku" +subtitle = "Ažurirajte lozinku za" +newPassword = "Nova lozinka" +confirmPassword = "Potvrdite lozinku" +placeholder = "Unesite novu lozinku" +confirmPlaceholder = "Ponovo unesite novu lozinku" +passwordRequired = "Unesite novu lozinku" +passwordMismatch = "Lozinke se ne poklapaju" +generateRandom = "Generišite bezbednu lozinku" +generatedPreview = "Generisana lozinka:" +copyTooltip = "Kopirajte u privremenu memoriju" +copiedToClipboard = "Lozinka je kopirana u privremenu memoriju" +copyFailed = "Nije uspelo kopiranje lozinke" +sendEmail = "Pošaljite korisniku email o ovoj promeni" +includePassword = "Uključite novu lozinku u email" +forcePasswordChange = "Naterajte korisnika da promeni lozinku pri sledećoj prijavi" +emailUnavailable = "Email ovog korisnika nije važeća email adresa. Obaveštenja su onemogućena." +smtpDisabled = "Email obaveštenja zahtevaju da SMTP bude omogućen u podešavanjima." +notifyOnly = "Biće poslat email bez lozinke, kako bi korisnik znao da je administrator promenio lozinku." +submit = "Ažurirajte lozinku" +success = "Lozinka je uspešno ažurirana" +error = "Ažuriranje lozinke nije uspelo" + [workspace.people.emailInvite] tab = "Poziv emailom" description = "Ukucajte ili nalepite email adrese ispod, odvojene zarezima. Korisnici će dobiti pristupne podatke putem emaila." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Potrebna je bar jedna email adresa" submit = "Pošalji pozive" success = "Korisnik(ci) uspešno pozvan(i)" -partialSuccess = "Neki pozivi nisu uspeli" +partialFailure = "Neki pozivi nisu uspeli" allFailed = "Pozivanje korisnika nije uspelo" error = "Slanje poziva nije uspelo" @@ -5770,6 +5925,7 @@ subtitle = "Prijavite se svojim Stirling nalogom" [setup.selfhosted] title = "Prijava na server" subtitle = "Unesite kredencijale servera" +link = "ili se povežite na samohostovani nalog" [setup.server] title = "Poveži se na server" @@ -5788,6 +5944,14 @@ description = "Unesite puni URL vašeg samohostovanog Stirling PDF servera" emptyUrl = "Unesite URL servera" unreachable = "Nije moguće povezati se sa serverom" testFailed = "Test veze nije uspeo" +configFetch = "Nije uspelo preuzimanje konfiguracije servera. Proverite URL i pokušajte ponovo." + +[setup.server.error.securityDisabled] +title = "Prijavljivanje nije omogućeno" +body = "Na ovom serveru prijavljivanje nije omogućeno. Da biste se povezali na ovaj server, morate omogućiti autentikaciju:" +step1 = "Postavite DOCKER_ENABLE_SECURITY=true u svom okruženju" +step2 = "Ili postavite security.enableLogin=true u settings.yml" +step3 = "Restartujte server" [setup.login] title = "Prijava" @@ -5797,6 +5961,13 @@ submit = "Prijavi se" signInWith = "Prijavite se sa" oauthPending = "Otvaranje pregledača za autentikaciju..." orContinueWith = "Ili nastavite uz email" +serverRequirement = "Napomena: Server mora imati omogućenu prijavu." +showInstructions = "Kako omogućiti?" +hideInstructions = "Sakrij uputstva" +instructions = "Da biste omogućili prijavu na svom Stirling PDF serveru:" +instructionsEnvVar = "Podesite promenljivu okruženja:" +instructionsOrYml = "Ili u settings.yml:" +instructionsRestart = "Zatim restartujte server da bi izmene stupile na snagu." [setup.login.username] label = "Korisničko ime" @@ -5853,6 +6024,7 @@ earlyAccess = "Rani pristup" reset = "Poništi izmene" downloadJson = "Preuzmi JSON" generatePdf = "Generiši PDF" +saveChanges = "Sačuvajte izmene" [pdfTextEditor.options.autoScaleText] title = "Automatski prilagodi tekst okvirima" @@ -5890,6 +6062,8 @@ alpha = "Ovaj alfa pregledač je i dalje u razvoju—određeni fontovi, boje, ef [pdfTextEditor.empty] title = "Nijedan dokument nije učitan" subtitle = "Učitajte PDF ili JSON datoteku da biste počeli sa uređivanjem teksta." +dropzone = "Prevucite i otpustite PDF ili JSON datoteku ovde ili kliknite da izaberete" +dropzoneWithFiles = "Izaberite datoteku sa kartice Datoteke, ili prevucite i otpustite PDF ili JSON datoteku ovde, ili kliknite da izaberete" [pdfTextEditor.welcomeBanner] title = "Dobrodošli u Uređivač teksta za PDF (rani pristup)" diff --git a/frontend/public/locales/sv-SE/translation.toml b/frontend/public/locales/sv-SE/translation.toml index 3cf3913ca..ac154f141 100644 --- a/frontend/public/locales/sv-SE/translation.toml +++ b/frontend/public/locales/sv-SE/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Ta bort från favoriter" fullscreen = "Byt till helskärmsläge" sidebar = "Byt till sidopanelläge" +[backendStartup] +notFoundTitle = "Backend hittades inte" +retry = "Försök igen" +unreachable = "Applikationen kan för närvarande inte ansluta till backend. Kontrollera backendens status och nätverksanslutningen och försök sedan igen." + [zipWarning] title = "Stor ZIP-fil" message = "Denna ZIP innehåller {{count}} filer. Extrahera ändå?" @@ -912,6 +917,9 @@ desc = "Skapa flerstegade arbetsflöden genom att kedja ihop PDF‑åtgärder. P desc = "Överlagrar PDF:er ovanpå en annan PDF" title = "Överlagra PDF:er" +[home.pdfTextEditor] +title = "PDF-textredigerare" +desc = "Redigera befintlig text och bilder i PDF-filer" [home.addText] tags = "text,kommentar,etikett" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Ritad signatur" defaultImageLabel = "Uppladdad signatur" defaultTextLabel = "Skriven signatur" saveButton = "Spara signatur" +savePersonal = "Spara som personlig" +saveShared = "Spara som delad" saveUnavailable = "Skapa en signatur först för att kunna spara." noChanges = "Aktuell signatur är redan sparad." +tempStorageTitle = "Tillfällig lagring i webbläsaren" +tempStorageDescription = "Signaturer lagras endast i din webbläsare. De går förlorade om du rensar webbläsardata eller byter webbläsare." +personalHeading = "Personliga signaturer" +sharedHeading = "Delade signaturer" +personalDescription = "Endast du kan se dessa signaturer." +sharedDescription = "Alla användare kan se och använda dessa signaturer." [sign.saved.type] canvas = "Ritning" @@ -2281,7 +2297,7 @@ placeDesc = "Placera signaturen på din PDF" [sign.type] title = "Signaturtyp" draw = "Rita" -canvas = "Canvas" +canvas = "Rityta" image = "Bild" text = "Text" saved = "Sparad" @@ -3020,6 +3036,91 @@ title = "Hämta information om PDF" header = "Hämta information om PDF" submit = "Hämta information" downloadJson = "Ladda ner JSON" +processing = "Extraherar information..." +results = "Resultat" +noResults = "Kör verktyget för att skapa en rapport." +downloads = "Nedladdningar" +noneDetected = "Inget upptäckt" +indexTitle = "Index" + +[getPdfInfo.report] +entryLabel = "Fullständig informationssammanfattning" +shortTitle = "PDF-information" + +[getPdfInfo.sections] +metadata = "Metadata" +formFields = "Formulärfält" +basicInfo = "Grundläggande info" +documentInfo = "Dokumentinformation" +compliance = "Efterlevnad" +encryption = "Kryptering" +permissions = "Behörigheter" +other = "Övrigt" +perPageInfo = "Information per sida" +tableOfContents = "Innehållsförteckning" + +[getPdfInfo.other] +attachments = "Bilagor" +embeddedFiles = "Inbäddade filer" +javaScript = "JavaScript" +layers = "Lager" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Storlek" +annotations = "Anteckningar" +images = "Bilder" +links = "Länkar" +fonts = "Typsnitt" +xobjects = "Antal XObject" +multimedia = "Multimedia" + +[getPdfInfo.summary] +pages = "Sidor" +fileSize = "Filstorlek" +pdfVersion = "PDF-version" +language = "Språk" +title = "PDF-sammanfattning" +author = "Författare" +created = "Skapad" +modified = "Ändrad" +permsAll = "Alla behörigheter tillåtna" +permsRestricted = "{{count}} begränsningar" +permsMixed = "Vissa behörigheter begränsade" +hasCompliance = "Har efterlevnadsstandarder" +noCompliance = "Inga efterlevnadsstandarder" +basic = "Grundläggande information" +documentInfo = "Dokumentinformation" +securityTitle = "Säkerhetsstatus" +technical = "Tekniskt" +overviewTitle = "PDF-översikt" + +[getPdfInfo.summary.security] +encrypted = "Krypterad PDF - Lösenordsskydd finns" +unencrypted = "Okrypterad PDF - Inget lösenordsskydd" + +[getPdfInfo.summary.tech] +images = "Bilder" +fonts = "Typsnitt" +formFields = "Formulärfält" +embeddedFiles = "Inbäddade filer" +javaScript = "JavaScript" +layers = "Lager" +bookmarks = "Bokmärken" +multimedia = "Multimedia" + +[getPdfInfo.summary.overview] +untitled = "ett namnlöst dokument" +unknown = "Okänd författare" +text = "Detta är en PDF på {{pages}} sidor med titeln {{title}}, skapad av {{author}} (PDF-version {{version}})." + +[getPdfInfo.error] +partial = "Vissa filer kunde inte bearbetas." +unexpected = "Oväntat fel under extrahering." + +[getPdfInfo.status] +complete = "Extrahering klar" [extractPage] tags = "extrahera" @@ -3438,6 +3539,9 @@ signinTitle = "Vänligen logga in" ssoSignIn = "Logga in via enkel inloggning" oAuth2AutoCreateDisabled = "OAUTH2 Auto-skapa användare inaktiverad" oAuth2AdminBlockedUser = "Registrering eller inloggning av icke-registrerade användare är för närvarande blockerad. Kontakta administratören." +oAuth2RequiresLicense = "OAuth/SSO-inloggning kräver en betald licens (Server eller Enterprise). Kontakta administratören för att uppgradera din plan." +saml2RequiresLicense = "SAML-inloggning kräver en betald licens (Server eller Enterprise). Kontakta administratören för att uppgradera din plan." +maxUsersReached = "Maximalt antal användare har uppnåtts för din nuvarande licens. Kontakta administratören för att uppgradera din plan eller lägga till fler användarplatser." oauth2RequestNotFound = "Auktoriseringsbegäran hittades inte" oauth2InvalidUserInfoResponse = "Ogiltigt svar på användarinformation" oauth2invalidRequest = "Ogiltig begäran" @@ -3846,14 +3950,17 @@ fitToWidth = "Anpassa till bredd" actualSize = "Faktisk storlek" [viewer] +cannotPreviewFile = "Kan inte förhandsgranska filen" +dualPageView = "Dubbelsidig vy" firstPage = "Första sidan" lastPage = "Sista sidan" -previousPage = "Föregående sida" nextPage = "Nästa sida" +onlyPdfSupported = "Visaren stöder endast PDF-filer. Den här filen verkar vara i ett annat format." +previousPage = "Föregående sida" +singlePageView = "Ensidig vy" +unknownFile = "Okänd fil" zoomIn = "Zooma in" zoomOut = "Zooma ut" -singlePageView = "Ensidig vy" -dualPageView = "Dubbelsidig vy" [rightRail] closeSelected = "Stäng markerade filer" @@ -3877,6 +3984,7 @@ toggleSidebar = "Växla sidofält" exportSelected = "Exportera markerade sidor" toggleAnnotations = "Växla synlighet för anteckningar" annotationMode = "Växla anteckningsläge" +print = "Skriv ut PDF" draw = "Rita" save = "Spara" saveChanges = "Spara ändringar" @@ -4494,6 +4602,7 @@ description = "URL eller filnamn till impressum (krävs i vissa jurisdiktioner)" title = "Premium och Enterprise" description = "Konfigurera din premium- eller enterprise-licensnyckel." license = "Licenskonfiguration" +noInput = "Ange en licensnyckel eller fil" [admin.settings.premium.licenseKey] toggle = "Har du en licensnyckel eller certifikatfil?" @@ -4511,6 +4620,25 @@ line1 = "Att skriva över din nuvarande licensnyckel kan inte ångras." line2 = "Din tidigare licens går förlorad permanent om du inte har säkerhetskopierat den någon annanstans." line3 = "Viktigt: Håll licensnycklar privata och säkra. Dela dem aldrig offentligt." +[admin.settings.premium.inputMethod] +text = "Licensnyckel" +file = "Certifikatfil" + +[admin.settings.premium.file] +label = "Licenscertifikatfil" +description = "Ladda upp din .lic- eller .cert-licensfil från offlineköp" +choose = "Välj licensfil" +selected = "Vald: {{filename}} ({{size}})" +successMessage = "Licensfilen har laddats upp och aktiverats. Ingen omstart krävs." + +[admin.settings.premium.currentLicense] +title = "Aktiv licens" +file = "Källa: Licensfil ({{path}})" +key = "Källa: Licensnyckel" +type = "Typ: {{type}}" +noInput = "Ange en licensnyckel eller ladda upp en certifikatfil" +success = "Lyckat" + [admin.settings.premium.enabled] label = "Aktivera premiumfunktioner" description = "Aktivera licensnyckelkontroller för pro-/enterprise-funktioner" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} markerade" download = "Ladda ner" delete = "Radera" unsupported = "Stöds inte" +active = "Aktiv" addToUpload = "Lägg till i uppladdning" +closeFile = "Stäng fil" deleteAll = "Ta bort alla" loadingFiles = "Läser in filer..." noFiles = "Inga filer tillgängliga" @@ -5132,7 +5262,7 @@ upgrade = "Uppgradera nu →" freeTitle = "Serverlicens" overLimitTitle = "Serverlicens krävs" overLimitBody = "Vår licensiering tillåter upp till {{freeTierLimit}} användare gratis per server. Du har {{overLimitUserCopy}} Stirling-användare. För att fortsätta utan avbrott, uppgradera till Stirling Server-planen - obegränsade platser, PDF-textredigering och full adminkontroll för $99/server/mån." -freeBody = "Vår Open-Core-licens tillåter upp till {{freeTierLimit}} användare gratis per server. För att skala utan avbrott och få tidig åtkomst till vårt nya PDF-textredigeringsverktyg rekommenderar vi Stirling Server-planen - full redigering och obegränsade platser för $99/server/mån." +freeBody = "Vår Open-Core-licens tillåter upp till {{freeTierLimit}} användare gratis per server. För att skala utan avbrott rekommenderar vi Stirling Server plan - obegränsat antal platser och SSO-stöd för $99/server/månad." [onboarding.desktopInstall] title = "Ladda ner" @@ -5237,6 +5367,31 @@ error = "Misslyckades med att uppdatera användarstatus" success = "Användare borttagen" error = "Misslyckades med att ta bort användare" +[workspace.people.changePassword] +action = "Byt lösenord" +title = "Byt lösenord" +subtitle = "Uppdatera lösenordet för" +newPassword = "Nytt lösenord" +confirmPassword = "Bekräfta lösenord" +placeholder = "Ange ett nytt lösenord" +confirmPlaceholder = "Ange det nya lösenordet igen" +passwordRequired = "Ange ett nytt lösenord" +passwordMismatch = "Lösenorden matchar inte" +generateRandom = "Generera säkert lösenord" +generatedPreview = "Genererat lösenord:" +copyTooltip = "Kopiera till urklipp" +copiedToClipboard = "Lösenord kopierat till urklipp" +copyFailed = "Det gick inte att kopiera lösenordet" +sendEmail = "Skicka e-post till användaren om denna ändring" +includePassword = "Inkludera det nya lösenordet i e-postmeddelandet" +forcePasswordChange = "Tvinga användaren att byta lösenord vid nästa inloggning" +emailUnavailable = "Den här användarens e-postadress är inte giltig. Aviseringar är inaktiverade." +smtpDisabled = "E-postaviseringar kräver att SMTP är aktiverat i inställningarna." +notifyOnly = "Ett e-postmeddelande skickas utan lösenordet och informerar användaren om att en admin har ändrat det." +submit = "Uppdatera lösenord" +success = "Lösenordet har uppdaterats" +error = "Det gick inte att uppdatera lösenordet" + [workspace.people.emailInvite] tab = "E-postinbjudan" description = "Skriv eller klistra in e-postadresser nedan, separerade med kommatecken. Användare får inloggningsuppgifter via e-post." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Minst en e-postadress krävs" submit = "Skicka inbjudningar" success = "Användare inbjudna" -partialSuccess = "Vissa inbjudningar misslyckades" +partialFailure = "Vissa inbjudningar misslyckades" allFailed = "Misslyckades med att bjuda in användare" error = "Misslyckades med att skicka inbjudningar" @@ -5770,6 +5925,7 @@ subtitle = "Logga in med ditt Stirling-konto" [setup.selfhosted] title = "Logga in på server" subtitle = "Ange dina serveruppgifter" +link = "eller anslut till ett självhostat konto" [setup.server] title = "Anslut till server" @@ -5788,6 +5944,14 @@ description = "Ange den fullständiga URL:en till din självhostade Stirling PDF emptyUrl = "Ange en server-URL" unreachable = "Kunde inte ansluta till servern" testFailed = "Anslutningstest misslyckades" +configFetch = "Det gick inte att hämta serverkonfigurationen. Kontrollera URL:en och försök igen." + +[setup.server.error.securityDisabled] +title = "Inloggning inte aktiverad" +body = "Den här servern har inte inloggning aktiverad. För att ansluta till den här servern måste du aktivera autentisering:" +step1 = "Ställ in DOCKER_ENABLE_SECURITY=true i din miljö" +step2 = "Eller ställ in security.enableLogin=true i settings.yml" +step3 = "Starta om servern" [setup.login] title = "Logga in" @@ -5797,6 +5961,13 @@ submit = "Logga in" signInWith = "Logga in med" oauthPending = "Öppnar webbläsaren för autentisering..." orContinueWith = "Eller fortsätt med e-post" +serverRequirement = "Observera: Servern måste ha inloggning aktiverad." +showInstructions = "Hur aktiverar man?" +hideInstructions = "Dölj instruktioner" +instructions = "För att aktivera inloggning på din Stirling PDF-server:" +instructionsEnvVar = "Ställ in miljövariabeln:" +instructionsOrYml = "Eller i settings.yml:" +instructionsRestart = "Starta sedan om servern för att ändringarna ska börja gälla." [setup.login.username] label = "Användarnamn" @@ -5853,6 +6024,7 @@ earlyAccess = "Tidig åtkomst" reset = "Återställ ändringar" downloadJson = "Ladda ner JSON" generatePdf = "Skapa PDF" +saveChanges = "Spara ändringar" [pdfTextEditor.options.autoScaleText] title = "Skala text automatiskt för att passa rutor" @@ -5890,6 +6062,8 @@ alpha = "Denna alfa-visare utvecklas fortfarande—vissa typsnitt, färger, tran [pdfTextEditor.empty] title = "Inget dokument inläst" subtitle = "Ladda en PDF- eller JSON-fil för att börja redigera textinnehåll." +dropzone = "Dra och släpp en PDF- eller JSON-fil här, eller klicka för att bläddra" +dropzoneWithFiles = "Välj en fil från fliken Filer, eller dra och släpp en PDF- eller JSON-fil här, eller klicka för att bläddra" [pdfTextEditor.welcomeBanner] title = "Välkommen till PDF Text Editor (Tidig åtkomst)" diff --git a/frontend/public/locales/th-TH/translation.toml b/frontend/public/locales/th-TH/translation.toml index bfd0077b2..eea7ee0d6 100644 --- a/frontend/public/locales/th-TH/translation.toml +++ b/frontend/public/locales/th-TH/translation.toml @@ -163,6 +163,11 @@ unfavorite = "นำออกจากรายการโปรด" fullscreen = "สลับเป็นโหมดเต็มหน้าจอ" sidebar = "สลับเป็นโหมดแถบด้านข้าง" +[backendStartup] +notFoundTitle = "ไม่พบ Backend" +retry = "ลองอีกครั้ง" +unreachable = "ขณะนี้แอปพลิเคชันไม่สามารถเชื่อมต่อกับ Backend ได้ โปรดตรวจสอบสถานะของ Backend และการเชื่อมต่อเครือข่าย จากนั้นลองอีกครั้ง" + [zipWarning] title = "ไฟล์ ZIP ขนาดใหญ่" message = "ZIP นี้มี {{count}} ไฟล์ ต้องการแตกไฟล์ต่อหรือไม่?" @@ -347,7 +352,7 @@ teams = "ทีม" title = "การกำหนดค่า" systemSettings = "การตั้งค่าระบบ" features = "ฟีเจอร์" -endpoints = "Endpoints" +endpoints = "ปลายทาง" database = "ฐานข้อมูล" advanced = "ขั้นสูง" @@ -369,7 +374,7 @@ privacy = "ความเป็นส่วนตัว" [settings.developer] title = "นักพัฒนา" -apiKeys = "API Keys" +apiKeys = "คีย์ API" [settings.tooltips] enableLoginFirst = "เปิดใช้งานโหมดเข้าสู่ระบบก่อน" @@ -556,7 +561,7 @@ totalEndpoints = "จำนวน Endpoint ทั้งหมด" totalVisits = "จำนวนการเข้าชมทั้งหมด" showing = "กำลังแสดง" selectedVisits = "การเข้าชมที่เลือก" -endpoint = "Endpoint" +endpoint = "ปลายทาง" visits = "การเข้าชม" percentage = "เปอร์เซ็นต์" loading = "กำลังโหลด..." @@ -794,7 +799,7 @@ title = "รวมเป็น หน้าเดียว" desc = "รวมหน้าทั้งหมดของ PDF เป็นหน้าเดียวขนาดใหญ่" [home.showJS] -tags = "javascript,code,script" +tags = "javascript,โค้ด,สคริปต์" title = "แสดง Javascript" desc = "ค้นหาและแสดง Javascript ที่ฝังใน PDF" @@ -829,7 +834,7 @@ title = "ตรวจสอบลายเซ็น PDF" desc = "ตรวจสอบลายเซ็นดิจิทัลและใบรับรองในเอกสาร PDF" [home.swagger] -tags = "API,documentation,test" +tags = "API,เอกสารประกอบ,ทดสอบ" title = "เอกสาร API" desc = "ดูเอกสาร API และทดสอบเอ็นด์พอยต์" @@ -878,7 +883,7 @@ title = "แทนที่และกลับสี" desc = "แทนที่หรือกลับสีในเอกสาร PDF" [home.devApi] -tags = "API,development,documentation" +tags = "API,การพัฒนา,เอกสารประกอบ" title = "API" desc = "ลิงก์ไปยังเอกสาร API" @@ -912,9 +917,12 @@ desc = "สร้างเวิร์กโฟลว์หลายขั้น desc = "ซ้อนทับ PDF บน PDF อีกไฟล์หนึ่ง" title = "ซ้อนทับ PDF" +[home.pdfTextEditor] +title = "ตัวแก้ไขข้อความ PDF" +desc = "แก้ไขข้อความและรูปภาพที่มีอยู่ภายในไฟล์ PDF" [home.addText] -tags = "text,annotation,label" +tags = "ข้อความ,คำอธิบายประกอบ,ป้ายกำกับ" title = "เพิ่มข้อความ" desc = "เพิ่มข้อความที่กำหนดเองที่ใดก็ได้ใน PDF ของคุณ" @@ -1213,9 +1221,9 @@ pdfaDigitalSignatureWarning = "PDF มีลายเซ็นดิจิทั fileFormat = "รูปแบบไฟล์" wordDoc = "เอกสาร Word" wordDocExt = "เอกสาร Word (.docx)" -odtExt = "OpenDocument Text (.odt)" +odtExt = "ข้อความ OpenDocument (.odt)" pptExt = "PowerPoint (.pptx)" -odpExt = "OpenDocument Presentation (.odp)" +odpExt = "งานนำเสนอ OpenDocument (.odp)" txtExt = "ข้อความล้วน (.txt)" rtfExt = "Rich Text Format (.rtf)" selectedFiles = "ไฟล์ที่เลือก" @@ -1832,7 +1840,7 @@ title = "ขั้นสูง" tags = "ย่อ, เล็ก, จิ๋ว" [unlockPDFForms] -tags = "remove,delete,form,field,readonly" +tags = "เอาออก,ลบ,ฟอร์ม,ฟิลด์,อ่านอย่างเดียว" title = "ลบสถานะอ่านอย่างเดียวออกจากช่องฟอร์ม" header = "ปลดล็อกฟอร์ม PDF" submit = "Remove" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "ลายเซ็นแบบวาด" defaultImageLabel = "ลายเซ็นที่อัปโหลด" defaultTextLabel = "ลายเซ็นแบบพิมพ์" saveButton = "บันทึกลายเซ็น" +savePersonal = "บันทึกส่วนตัว" +saveShared = "บันทึกแบบใช้ร่วมกัน" saveUnavailable = "สร้างลายเซ็นก่อนเพื่อบันทึก" noChanges = "ลายเซ็นปัจจุบันถูกบันทึกไว้แล้ว" +tempStorageTitle = "พื้นที่จัดเก็บชั่วคราวบนเบราว์เซอร์" +tempStorageDescription = "ลายเซ็นจะถูกจัดเก็บไว้ในเบราว์เซอร์ของคุณเท่านั้น และจะหายไปหากคุณล้างข้อมูลเบราว์เซอร์หรือสลับไปใช้เบราว์เซอร์อื่น" +personalHeading = "ลายเซ็นส่วนตัว" +sharedHeading = "ลายเซ็นที่ใช้ร่วมกัน" +personalDescription = "มีเพียงคุณเท่านั้นที่มองเห็นลายเซ็นเหล่านี้" +sharedDescription = "ผู้ใช้ทุกคนสามารถเห็นและใช้ลายเซ็นเหล่านี้ได้" [sign.saved.type] canvas = "การวาด" @@ -2731,7 +2747,7 @@ submit = "ส่ง" failed = "เกิดข้อผิดพลาดขณะสร้างเลย์เอาต์หลายหน้า" [bookletImposition] -tags = "booklet,imposition,printing,binding,folding,signature" +tags = "หนังสือเย็บเล่ม,การจัดหน้า,การพิมพ์,การเข้าเล่ม,การพับ,ชุดพิมพ์" title = "การจัดหน้าสมุด" header = "การจัดหน้าสมุด" submit = "สร้างสมุด" @@ -2830,7 +2846,7 @@ scaleFactor = "ระดับการซูม (ครอบตัด) ขอ submit = "ส่ง" [adjustPageScale] -tags = "resize,modify,dimension,adapt" +tags = "ปรับขนาด,แก้ไข,ขนาด,ปรับให้เหมาะสม" title = "ปรับสเกลหน้า" header = "ปรับสเกลหน้า" submit = "ปรับสเกลหน้า" @@ -3020,6 +3036,91 @@ title = "รับข้อมูลเกี่ยวกับ PDF" header = "รับข้อมูลเกี่ยวกับ PDF" submit = "รับข้อมูล" downloadJson = "ดาวน์โหลด JSON" +processing = "กำลังแยกข้อมูล..." +results = "ผลลัพธ์" +noResults = "เรียกใช้เครื่องมือนี้เพื่อสร้างรายงาน" +downloads = "ดาวน์โหลด" +noneDetected = "ไม่พบ" +indexTitle = "ดัชนี" + +[getPdfInfo.report] +entryLabel = "สรุปข้อมูลทั้งหมด" +shortTitle = "ข้อมูล PDF" + +[getPdfInfo.sections] +metadata = "ข้อมูลเมตา" +formFields = "เขตข้อมูลฟอร์ม" +basicInfo = "ข้อมูลพื้นฐาน" +documentInfo = "ข้อมูลเอกสาร" +compliance = "การปฏิบัติตามมาตรฐาน" +encryption = "การเข้ารหัส" +permissions = "สิทธิ์" +other = "อื่นๆ" +perPageInfo = "ข้อมูลรายหน้า" +tableOfContents = "สารบัญ" + +[getPdfInfo.other] +attachments = "ไฟล์แนบ" +embeddedFiles = "ไฟล์ฝัง" +javaScript = "JavaScript" +layers = "เลเยอร์" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "ขนาด" +annotations = "คำอธิบายประกอบ" +images = "รูปภาพ" +links = "ลิงก์" +fonts = "ฟอนต์" +xobjects = "จำนวน XObject" +multimedia = "มัลติมีเดีย" + +[getPdfInfo.summary] +pages = "หน้า" +fileSize = "ขนาดไฟล์" +pdfVersion = "เวอร์ชัน PDF" +language = "ภาษา" +title = "สรุป PDF" +author = "ผู้เขียน" +created = "สร้างเมื่อ" +modified = "แก้ไขเมื่อ" +permsAll = "อนุญาตสิทธิ์ทั้งหมด" +permsRestricted = "ข้อจำกัด {{count}} รายการ" +permsMixed = "มีการจำกัดสิทธิ์บางอย่าง" +hasCompliance = "มีมาตรฐานการปฏิบัติตาม" +noCompliance = "ไม่มีมาตรฐานการปฏิบัติตาม" +basic = "ข้อมูลพื้นฐาน" +documentInfo = "ข้อมูลเอกสาร" +securityTitle = "สถานะความปลอดภัย" +technical = "เชิงเทคนิค" +overviewTitle = "ภาพรวมของ PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF เข้ารหัส - มีการป้องกันด้วยรหัสผ่าน" +unencrypted = "PDF ไม่เข้ารหัส - ไม่มีการป้องกันรหัสผ่าน" + +[getPdfInfo.summary.tech] +images = "รูปภาพ" +fonts = "ฟอนต์" +formFields = "เขตข้อมูลฟอร์ม" +embeddedFiles = "ไฟล์ฝัง" +javaScript = "JavaScript" +layers = "เลเยอร์" +bookmarks = "ที่คั่นหน้า" +multimedia = "มัลติมีเดีย" + +[getPdfInfo.summary.overview] +untitled = "เอกสารที่ไม่มีชื่อ" +unknown = "ผู้เขียนไม่ทราบ" +text = "นี่คือไฟล์ PDF จำนวน {{pages}} หน้า ชื่อ {{title}} สร้างโดย {{author}} (เวอร์ชัน PDF {{version}})." + +[getPdfInfo.error] +partial = "ไม่สามารถประมวลผลไฟล์บางไฟล์ได้" +unexpected = "เกิดข้อผิดพลาดที่ไม่คาดคิดระหว่างการแยกข้อมูล" + +[getPdfInfo.status] +complete = "การแยกข้อมูลเสร็จสิ้น" [extractPage] tags = "แยก" @@ -3438,6 +3539,9 @@ signinTitle = "กรุณาลงชื่อเข้าใช้" ssoSignIn = "เข้าสู่ระบบด้วย Single Sign-on" oAuth2AutoCreateDisabled = "การสร้างผู้ใช้ OAuth2 อัตโนมัติถูกปิดใช้งาน" oAuth2AdminBlockedUser = "ขณะนี้มีการบล็อกการลงทะเบียนหรือการเข้าสู่ระบบของผู้ใช้ที่ไม่ได้ลงทะเบียน โปรดติดต่อผู้ดูแลระบบ" +oAuth2RequiresLicense = "การเข้าสู่ระบบด้วย OAuth/SSO ต้องมีไลเซนส์แบบชำระเงิน (Server หรือ Enterprise) โปรดติดต่อผู้ดูแลระบบเพื่ออัปเกรดแผนของคุณ" +saml2RequiresLicense = "การเข้าสู่ระบบด้วย SAML ต้องมีไลเซนส์แบบชำระเงิน (Server หรือ Enterprise) โปรดติดต่อผู้ดูแลระบบเพื่ออัปเกรดแผนของคุณ" +maxUsersReached = "จำนวนผู้ใช้ถึงขีดสูงสุดสำหรับไลเซนส์ปัจจุบันของคุณ โปรดติดต่อผู้ดูแลระบบเพื่ออัปเกรดแผนหรือเพิ่มจำนวนที่นั่ง" oauth2RequestNotFound = "ไม่พบคำขอการอนุญาต" oauth2InvalidUserInfoResponse = "การตอบกลับข้อมูลผู้ใช้ไม่ถูกต้อง" oauth2invalidRequest = "คำขอไม่ถูกต้อง" @@ -3771,7 +3875,7 @@ version = "รุ่นปัจจุบัน" title = "เอกสาร API" header = "เอกสาร API" desc = "ดูและทดสอบจุดปลายทาง API ของ Stirling PDF" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,เอกสารประกอบ,swagger,ปลายทาง,การพัฒนา" [cookieBanner.popUp] title = "เราใช้คุกกี้อย่างไร" @@ -3846,14 +3950,17 @@ fitToWidth = "พอดีกับความกว้าง" actualSize = "ขนาดจริง" [viewer] +cannotPreviewFile = "ไม่สามารถแสดงตัวอย่างไฟล์ได้" +dualPageView = "มุมมองสองหน้า" firstPage = "หน้าแรก" lastPage = "หน้าสุดท้าย" -previousPage = "หน้าก่อนหน้า" nextPage = "หน้าถัดไป" +onlyPdfSupported = "ตัวแสดงผลรองรับเฉพาะไฟล์ PDF ไฟล์นี้ดูเหมือนจะเป็นรูปแบบอื่น" +previousPage = "หน้าก่อนหน้า" +singlePageView = "มุมมองหน้าเดียว" +unknownFile = "ไฟล์ไม่รู้จัก" zoomIn = "ซูมเข้า" zoomOut = "ซูมออก" -singlePageView = "มุมมองหน้าเดียว" -dualPageView = "มุมมองสองหน้า" [rightRail] closeSelected = "ปิดไฟล์ที่เลือก" @@ -3877,6 +3984,7 @@ toggleSidebar = "สลับแถบข้าง" exportSelected = "ส่งออกหน้าที่เลือก" toggleAnnotations = "สลับการแสดงคำอธิบายประกอบ" annotationMode = "สลับโหมดคำอธิบายประกอบ" +print = "พิมพ์ PDF" draw = "วาด" save = "บันทึก" saveChanges = "บันทึกการเปลี่ยนแปลง" @@ -4270,7 +4378,7 @@ label = "ผู้ให้บริการ" description = "ชื่อผู้ให้บริการ SAML2" [admin.settings.connections.saml2.registrationId] -label = "Registration ID" +label = "รหัสการลงทะเบียน" description = "ตัวระบุการลงทะเบียนของ SAML2" [admin.settings.connections.saml2.autoCreateUser] @@ -4343,7 +4451,7 @@ features = "แฟลกฟีเจอร์" processing = "การประมวลผล" [admin.settings.advanced.endpoints] -label = "Endpoints" +label = "ปลายทาง" manage = "จัดการ API Endpoints" description = "การจัดการ Endpoint กำหนดค่าผ่าน YAML ดูเอกสารประกอบสำหรับรายละเอียดการเปิด/ปิดใช้งาน Endpoint เฉพาะ" @@ -4407,7 +4515,7 @@ description = "จะทำความสะอาดไดเรกทอร label = "ข้อจำกัดของตัวประมวลผลกระบวนการ" description = "กำหนดขีดจำกัดเซสชันและระยะหมดเวลาสำหรับตัวประมวลผลแต่ละตัว" libreOffice = "LibreOffice" -pdfToHtml = "PDF to HTML" +pdfToHtml = "PDF เป็น HTML" qpdf = "QPDF" tesseract = "Tesseract OCR" pythonOpenCv = "Python OpenCV" @@ -4494,6 +4602,7 @@ description = "URL หรือชื่อไฟล์ไปยัง Impressum title = "พรีเมียมและเอนเตอร์ไพรส์" description = "กำหนดค่าคีย์ไลเซนส์พรีเมียมหรือเอนเตอร์ไพรส์ของคุณ" license = "การกำหนดค่าไลเซนส์" +noInput = "โปรดระบุคีย์หรือไฟล์ไลเซนส์" [admin.settings.premium.licenseKey] toggle = "มี license key หรือไฟล์ certificate ไหม?" @@ -4511,6 +4620,25 @@ line1 = "การเขียนทับ license key ปัจจุบัน line2 = "ไลเซนส์ก่อนหน้าของคุณจะสูญหายถาวร เว้นแต่คุณได้สำรองไว้ที่อื่น" line3 = "สำคัญ: เก็บ license keys ให้เป็นส่วนตัวและปลอดภัย ห้ามแชร์สาธารณะ" +[admin.settings.premium.inputMethod] +text = "คีย์ไลเซนส์" +file = "ไฟล์ใบรับรอง" + +[admin.settings.premium.file] +label = "ไฟล์ใบรับรองไลเซนส์" +description = "อัปโหลดไฟล์ไลเซนส์ .lic หรือ .cert จากการสั่งซื้อแบบออฟไลน์ของคุณ" +choose = "เลือกไฟล์ไลเซนส์" +selected = "ที่เลือก: {{filename}} ({{size}})" +successMessage = "อัปโหลดและเปิดใช้งานไฟล์ไลเซนส์สำเร็จ ไม่จำเป็นต้องเริ่มระบบใหม่" + +[admin.settings.premium.currentLicense] +title = "ไลเซนส์ที่ใช้งานอยู่" +file = "แหล่งที่มา: ไฟล์ไลเซนส์ ({{path}})" +key = "แหล่งที่มา: คีย์ไลเซนส์" +type = "ประเภท: {{type}}" +noInput = "โปรดระบุคีย์ไลเซนส์หรืออัปโหลดไฟล์ใบรับรอง" +success = "สำเร็จ" + [admin.settings.premium.enabled] label = "เปิดใช้งานฟีเจอร์พรีเมียม" description = "เปิดการตรวจสอบคีย์ไลเซนส์สำหรับฟีเจอร์แบบ Pro/Enterprise" @@ -4544,7 +4672,7 @@ label = "สร้างใหม่เมื่อเริ่มต้น" description = "สร้างใบรับรองใหม่ทุกครั้งที่แอปพลิเคชันเริ่มต้น" [admin.settings.endpoints] -title = "API Endpoints" +title = "ปลายทาง API" description = "ควบคุมว่า API Endpoints และกลุ่ม Endpoint ใดที่ใช้งานได้" management = "การจัดการ Endpoint" note = "หมายเหตุ: การปิดการใช้งาน endpoints จะจำกัดการเข้าถึง API แต่จะไม่ลบส่วนติดต่อผู้ใช้ ต้องรีสตาร์ทจึงจะมีผล" @@ -4644,7 +4772,9 @@ selectedCount = "เลือกแล้ว {{count}} รายการ" download = "ดาวน์โหลด" delete = "ลบ" unsupported = "ไม่รองรับ" +active = "ใช้งานอยู่" addToUpload = "เพิ่มไปยังอัปโหลด" +closeFile = "ปิดไฟล์" deleteAll = "ลบทั้งหมด" loadingFiles = "กำลังโหลดไฟล์..." noFiles = "ไม่มีไฟล์" @@ -4989,7 +5119,7 @@ chartAriaLabel = "การใช้เครดิต: ใช้แบบรว nextReset = "รีเซ็ตครั้งถัดไป" lastApiUse = "การใช้งาน API ล่าสุด" overlayMessage = "สร้างคีย์เพื่อดูเครดิตและเครดิตที่มีอยู่" -label = "API Key" +label = "คีย์ API" guestInfo = "ผู้ใช้แบบแขกจะไม่ได้รับ API key สร้างบัญชีเพื่อรับ API key สำหรับใช้งานในแอปพลิเคชันของคุณ" goToAccount = "ไปที่บัญชี" generateError = "ไม่สามารถสร้าง API key ของคุณได้" @@ -5132,7 +5262,7 @@ upgrade = "อัปเกรดเลย →" freeTitle = "ไลเซนส์เซิร์ฟเวอร์" overLimitTitle = "ต้องใช้ไลเซนส์เซิร์ฟเวอร์" overLimitBody = "สิทธิ์การใช้งานของเรารองรับผู้ใช้ได้ฟรีสูงสุด {{freeTierLimit}} คนต่อเซิร์ฟเวอร์ ขณะนี้คุณมีผู้ใช้ Stirling {{overLimitUserCopy}} คน เพื่อใช้งานต่อเนื่อง โปรดอัปเกรดเป็นแพ็กเกจ Stirling Server - ที่นั่งไม่จำกัด แก้ไขข้อความ PDF และควบคุมแอดมินเต็มรูปแบบ ราคา $99/ต่อเซิร์ฟเวอร์/เดือน" -freeBody = "ไลเซนส์แบบ Open-Core ของเรารองรับผู้ใช้ได้ฟรีสูงสุด {{freeTierLimit}} คนต่อเซิร์ฟเวอร์ เพื่อขยายการใช้งานได้ต่อเนื่องและเข้าถึง เครื่องมือแก้ไขข้อความ PDF ล่วงหน้า เราแนะนำแพ็กเกจ Stirling Server - แก้ไขได้เต็มรูปแบบและ ที่นั่งไม่จำกัด ราคา $99/ต่อเซิร์ฟเวอร์/เดือน" +freeBody = "สัญญาอนุญาตแบบ Open-Core ของเราอนุญาตให้ใช้งานฟรีได้สูงสุด {{freeTierLimit}} ผู้ใช้ต่อเซิร์ฟเวอร์หนึ่งเครื่อง เพื่อขยายการใช้งานอย่างต่อเนื่อง เราขอแนะนำแผน Stirling Server - ที่นั่งไม่จำกัด และ รองรับ SSO ในราคา $99/เซิร์ฟเวอร์/เดือน" [onboarding.desktopInstall] title = "ดาวน์โหลด" @@ -5237,6 +5367,31 @@ error = "อัปเดตสถานะผู้ใช้ไม่สำเ success = "ลบผู้ใช้เรียบร้อยแล้ว" error = "ลบผู้ใช้ไม่สำเร็จ" +[workspace.people.changePassword] +action = "เปลี่ยนรหัสผ่าน" +title = "เปลี่ยนรหัสผ่าน" +subtitle = "อัปเดตรหัสผ่านสำหรับ" +newPassword = "รหัสผ่านใหม่" +confirmPassword = "ยืนยันรหัสผ่าน" +placeholder = "ป้อนรหัสผ่านใหม่" +confirmPlaceholder = "ป้อนรหัสผ่านใหม่อีกครั้ง" +passwordRequired = "โปรดป้อนรหัสผ่านใหม่" +passwordMismatch = "รหัสผ่านไม่ตรงกัน" +generateRandom = "สร้างรหัสผ่านที่ปลอดภัย" +generatedPreview = "รหัสผ่านที่สร้างขึ้น:" +copyTooltip = "คัดลอกไปยังคลิปบอร์ด" +copiedToClipboard = "คัดลอกรหัสผ่านไปยังคลิปบอร์ดแล้ว" +copyFailed = "คัดลอกรหัสผ่านไม่สำเร็จ" +sendEmail = "ส่งอีเมลแจ้งผู้ใช้เกี่ยวกับการเปลี่ยนแปลงนี้" +includePassword = "รวมรหัสผ่านใหม่ในอีเมล" +forcePasswordChange = "บังคับให้ผู้ใช้เปลี่ยนรหัสผ่านในการเข้าสู่ระบบครั้งถัดไป" +emailUnavailable = "อีเมลของผู้ใช้นี้ไม่ใช่ที่อยู่อีเมลที่ถูกต้อง การแจ้งเตือนถูกปิดใช้งาน" +smtpDisabled = "การแจ้งเตือนทางอีเมลต้องเปิดใช้งาน SMTP ในการตั้งค่า" +notifyOnly = "จะมีการส่งอีเมลโดยไม่รวมรหัสผ่าน เพื่อแจ้งให้ผู้ใช้ทราบว่าผู้ดูแลระบบได้เปลี่ยนรหัสผ่านแล้ว" +submit = "อัปเดตรหัสผ่าน" +success = "อัปเดตรหัสผ่านสำเร็จ" +error = "อัปเดตรหัสผ่านไม่สำเร็จ" + [workspace.people.emailInvite] tab = "เชิญทางอีเมล" description = "พิมพ์หรือวางอีเมลด้านล่าง คั่นด้วยเครื่องหมายจุลภาค ผู้ใช้จะได้รับข้อมูลเข้าสู่ระบบทางอีเมล" @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "ต้องมีอย่างน้อยหนึ่งที่อยู่อีเมล" submit = "ส่งคำเชิญ" success = "เชิญผู้ใช้สำเร็จ" -partialSuccess = "บางคำเชิญล้มเหลว" +partialFailure = "คำเชิญบางรายการไม่สำเร็จ" allFailed = "เชิญผู้ใช้ไม่สำเร็จ" error = "ส่งคำเชิญไม่สำเร็จ" @@ -5709,7 +5864,7 @@ title = "แผนภูมิการใช้งาน Endpoint" [usage.table] title = "สถิติแบบละเอียด" -endpoint = "Endpoint" +endpoint = "ปลายทาง" visits = "การเข้าชม" percentage = "เปอร์เซ็นต์" noData = "ไม่มีข้อมูล" @@ -5770,6 +5925,7 @@ subtitle = "ลงชื่อเข้าใช้ด้วยบัญชี S [setup.selfhosted] title = "ลงชื่อเข้าใช้เซิร์ฟเวอร์" subtitle = "ป้อนข้อมูลรับรองของเซิร์ฟเวอร์ของคุณ" +link = "หรือเชื่อมต่อกับบัญชีแบบ self-hosted" [setup.server] title = "เชื่อมต่อกับเซิร์ฟเวอร์" @@ -5788,6 +5944,14 @@ description = "ป้อน URL แบบเต็มของเซิร์ฟ emptyUrl = "โปรดป้อน URL เซิร์ฟเวอร์" unreachable = "ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์" testFailed = "การทดสอบการเชื่อมต่อล้มเหลว" +configFetch = "ไม่สามารถดึงการกำหนดค่าของเซิร์ฟเวอร์ได้ โปรดตรวจสอบ URL แล้วลองอีกครั้ง" + +[setup.server.error.securityDisabled] +title = "ไม่ได้เปิดใช้งานการเข้าสู่ระบบ" +body = "เซิร์ฟเวอร์นี้ไม่ได้เปิดใช้งานการเข้าสู่ระบบ เพื่อเชื่อมต่อกับเซิร์ฟเวอร์นี้ คุณต้องเปิดใช้งานการยืนยันตัวตน:" +step1 = "กำหนดค่า DOCKER_ENABLE_SECURITY=true ในสภาพแวดล้อมของคุณ" +step2 = "หรือกำหนดค่า security.enableLogin=true ใน settings.yml" +step3 = "เริ่มเซิร์ฟเวอร์ใหม่" [setup.login] title = "เข้าสู่ระบบ" @@ -5797,6 +5961,13 @@ submit = "เข้าสู่ระบบ" signInWith = "ลงชื่อเข้าใช้ด้วย" oauthPending = "กำลังเปิดเบราว์เซอร์เพื่อยืนยันตัวตน..." orContinueWith = "หรือดำเนินการต่อด้วยอีเมล" +serverRequirement = "หมายเหตุ: เซิร์ฟเวอร์ต้องเปิดใช้งานการเข้าสู่ระบบ" +showInstructions = "เปิดใช้งานอย่างไร?" +hideInstructions = "ซ่อนคำแนะนำ" +instructions = "วิธีเปิดใช้งานการเข้าสู่ระบบบนเซิร์ฟเวอร์ Stirling PDF ของคุณ:" +instructionsEnvVar = "ตั้งค่าตัวแปรสภาพแวดล้อม:" +instructionsOrYml = "หรือใน settings.yml:" +instructionsRestart = "จากนั้นรีสตาร์ทเซิร์ฟเวอร์ของคุณเพื่อให้การเปลี่ยนแปลงมีผล" [setup.login.username] label = "ชื่อผู้ใช้" @@ -5853,6 +6024,7 @@ earlyAccess = "เข้าถึงล่วงหน้า" reset = "รีเซ็ตการเปลี่ยนแปลง" downloadJson = "ดาวน์โหลด JSON" generatePdf = "สร้าง PDF" +saveChanges = "บันทึกการเปลี่ยนแปลง" [pdfTextEditor.options.autoScaleText] title = "ปรับขนาดข้อความอัตโนมัติให้พอดีกล่อง" @@ -5890,6 +6062,8 @@ alpha = "ตัวดูแบบ alpha นี้ยังพัฒนาอย [pdfTextEditor.empty] title = "ยังไม่ได้โหลดเอกสาร" subtitle = "โหลดไฟล์ PDF หรือ JSON เพื่อเริ่มแก้ไขข้อความ" +dropzone = "ลากและวางไฟล์ PDF หรือ JSON ที่นี่ หรือคลิกเพื่อเรียกดู" +dropzoneWithFiles = "เลือกไฟล์จากแท็บไฟล์ หรือลากและวางไฟล์ PDF หรือ JSON ที่นี่ หรือคลิกเพื่อเรียกดู" [pdfTextEditor.welcomeBanner] title = "ยินดีต้อนรับสู่ PDF Text Editor (Early Access)" @@ -5932,13 +6106,13 @@ warnings = "คำเตือน" suggestions = "หมายเหตุ" currentPageFonts = "ฟอนต์บนหน้านี้" allFonts = "ฟอนต์ทั้งหมด" -fallback = "fallback" +fallback = "สำรอง" missing = "หายไป" perfectMessage = "สามารถทำซ้ำฟอนต์ทั้งหมดได้อย่างสมบูรณ์แบบ" warningMessage = "บางฟอนต์อาจแสดงผลไม่ถูกต้อง" infoMessage = "มีข้อมูลการทำซ้ำฟอนต์" perfect = "สมบูรณ์แบบ" -subset = "subset" +subset = "ซับเซ็ต" [pdfTextEditor.errors] invalidJson = "ไม่สามารถอ่านไฟล์ JSON โปรดตรวจสอบว่าไฟล์ถูกสร้างโดยเครื่องมือ PDF to JSON" @@ -5953,7 +6127,7 @@ insufficientPermissions = "คุณไม่มีสิทธิ์ในก [addText] title = "เพิ่มข้อความ" header = "เพิ่มข้อความลงใน PDF" -tags = "text,annotation,label" +tags = "ข้อความ,คำอธิบายประกอบ,ป้ายกำกับ" applySignatures = "ใช้ข้อความ" [addText.text] diff --git a/frontend/public/locales/tr-TR/translation.toml b/frontend/public/locales/tr-TR/translation.toml index a5bee2af7..43d9eb0cd 100644 --- a/frontend/public/locales/tr-TR/translation.toml +++ b/frontend/public/locales/tr-TR/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Favorilerden kaldır" fullscreen = "Tam ekran moduna geç" sidebar = "Kenar çubuğu moduna geç" +[backendStartup] +notFoundTitle = "Arka uç bulunamadı" +retry = "Yeniden dene" +unreachable = "Uygulama şu anda arka uca bağlanamıyor. Lütfen arka uç durumunu ve ağ bağlantısını doğrulayın, ardından tekrar deneyin." + [zipWarning] title = "Büyük ZIP Dosyası" message = "Bu ZIP {{count}} dosya içeriyor. Yine de çıkartılsın mı?" @@ -912,6 +917,9 @@ desc = "PDF eylemlerini birbirine bağlayarak çok adımlı iş akışları olu desc = "PDF'leri başka bir PDF'nin üzerine bindirir" title = "PDF'leri Bindirme" +[home.pdfTextEditor] +title = "PDF Metin Düzenleyici" +desc = "PDF'lerin içindeki mevcut metinleri ve görselleri düzenleyin" [home.addText] tags = "metin,ek açıklama,etiket" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Çizim imzası" defaultImageLabel = "Yüklenen imza" defaultTextLabel = "Yazılmış imza" saveButton = "İmzayı kaydet" +savePersonal = "Kişisel Olarak Kaydet" +saveShared = "Paylaşılan Olarak Kaydet" saveUnavailable = "Kaydetmek için önce bir imza oluşturun." noChanges = "Geçerli imza zaten kaydedildi." +tempStorageTitle = "Geçici tarayıcı depolaması" +tempStorageDescription = "İmzalar yalnızca tarayıcınızda saklanır. Tarayıcı verilerini temizlerseniz veya tarayıcı değiştirirseniz kaybolurlar." +personalHeading = "Kişisel İmzalar" +sharedHeading = "Paylaşılan İmzalar" +personalDescription = "Bu imzaları yalnızca siz görebilirsiniz." +sharedDescription = "Tüm kullanıcılar bu imzaları görebilir ve kullanabilir." [sign.saved.type] canvas = "Çizim" @@ -3020,6 +3036,91 @@ title = "PDF Hakkında Bilgi Al" header = "PDF Hakkında Bilgi Al" submit = "Bilgi Al" downloadJson = "JSON İndir" +processing = "Bilgi çıkarılıyor..." +results = "Sonuçlar" +noResults = "Bir rapor oluşturmak için aracı çalıştırın." +downloads = "İndirmeler" +noneDetected = "Hiçbiri tespit edilmedi" +indexTitle = "Dizin" + +[getPdfInfo.report] +entryLabel = "Tam bilgi özeti" +shortTitle = "PDF Bilgileri" + +[getPdfInfo.sections] +metadata = "Meta veriler" +formFields = "Form Alanları" +basicInfo = "Temel Bilgiler" +documentInfo = "Belge Bilgileri" +compliance = "Uyumluluk" +encryption = "Şifreleme" +permissions = "İzinler" +other = "Diğer" +perPageInfo = "Sayfa Başına Bilgi" +tableOfContents = "İçindekiler" + +[getPdfInfo.other] +attachments = "Ekler" +embeddedFiles = "Gömülü Dosyalar" +javaScript = "JavaScript" +layers = "Katmanlar" +structureTree = "Yapı Ağacı" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Boyut" +annotations = "Açıklamalar" +images = "Görüntüler" +links = "Bağlantılar" +fonts = "Yazı Tipleri" +xobjects = "XObject Sayıları" +multimedia = "Multimedya" + +[getPdfInfo.summary] +pages = "Sayfalar" +fileSize = "Dosya Boyutu" +pdfVersion = "PDF Sürümü" +language = "Dil" +title = "PDF Özeti" +author = "Yazar" +created = "Oluşturulma" +modified = "Değiştirilme" +permsAll = "Tüm izinlere izin verildi" +permsRestricted = "{{count}} kısıtlama" +permsMixed = "Bazı izinler kısıtlandı" +hasCompliance = "Uyumluluk standartlarına sahip" +noCompliance = "Uyumluluk standardı yok" +basic = "Temel Bilgiler" +documentInfo = "Belge Bilgileri" +securityTitle = "Güvenlik Durumu" +technical = "Teknik" +overviewTitle = "PDF Genel Bakış" + +[getPdfInfo.summary.security] +encrypted = "Şifrelenmiş PDF - Parola koruması var" +unencrypted = "Şifrelenmemiş PDF - Parola koruması yok" + +[getPdfInfo.summary.tech] +images = "Görüntüler" +fonts = "Yazı Tipleri" +formFields = "Form Alanları" +embeddedFiles = "Gömülü Dosyalar" +javaScript = "JavaScript" +layers = "Katmanlar" +bookmarks = "Yer İmleri" +multimedia = "Multimedya" + +[getPdfInfo.summary.overview] +untitled = "başlıksız bir belge" +unknown = "Bilinmeyen Yazar" +text = "Bu, {{author}} tarafından oluşturulan, {{title}} başlıklı {{pages}} sayfalık bir PDF'dir (PDF sürümü {{version}})." + +[getPdfInfo.error] +partial = "Bazı dosyalar işlenemedi." +unexpected = "Çıkarma sırasında beklenmeyen bir hata oluştu." + +[getPdfInfo.status] +complete = "Çıkarma tamamlandı" [extractPage] tags = "çıkar" @@ -3438,6 +3539,9 @@ signinTitle = "Lütfen giriş yapınız." ssoSignIn = "Tek Oturum Açma ile Giriş Yap" oAuth2AutoCreateDisabled = "OAUTH2 Otomatik Oluşturma Kullanıcı Devre Dışı Bırakıldı" oAuth2AdminBlockedUser = "Kayıtlı olmayan kullanıcıların kayıt veya giriş yapması şu anda engellenmiştir. Lütfen yöneticiyle iletişime geçin." +oAuth2RequiresLicense = "OAuth/SSO ile oturum açma ücretli bir lisans (Server veya Enterprise) gerektirir. Planınızı yükseltmek için lütfen yöneticiyle iletişime geçin." +saml2RequiresLicense = "SAML ile oturum açma ücretli bir lisans (Server veya Enterprise) gerektirir. Planınızı yükseltmek için lütfen yöneticiyle iletişime geçin." +maxUsersReached = "Mevcut lisansınız için azami kullanıcı sayısına ulaşıldı. Planınızı yükseltmek veya daha fazla koltuk eklemek için lütfen yöneticiyle iletişime geçin." oauth2RequestNotFound = "Yetkilendirme isteği bulunamadı" oauth2InvalidUserInfoResponse = "Geçersiz Kullanıcı Bilgisi Yanıtı" oauth2invalidRequest = "Geçersiz İstek" @@ -3846,14 +3950,17 @@ fitToWidth = "Genişliğe Sığdır" actualSize = "Gerçek Boyut" [viewer] +cannotPreviewFile = "Dosya önizlenemiyor" +dualPageView = "Çift Sayfa Görünümü" firstPage = "İlk Sayfa" lastPage = "Son Sayfa" -previousPage = "Önceki Sayfa" nextPage = "Sonraki Sayfa" +onlyPdfSupported = "Görüntüleyici yalnızca PDF dosyalarını destekler. Bu dosya farklı bir biçimde görünüyor." +previousPage = "Önceki Sayfa" +singlePageView = "Tek Sayfa Görünümü" +unknownFile = "Bilinmeyen dosya" zoomIn = "Yakınlaştır" zoomOut = "Uzaklaştır" -singlePageView = "Tek Sayfa Görünümü" -dualPageView = "Çift Sayfa Görünümü" [rightRail] closeSelected = "Seçilen Dosyaları Kapat" @@ -3877,6 +3984,7 @@ toggleSidebar = "Kenar Çubuğunu Aç/Kapat" exportSelected = "Seçilen Sayfaları Dışa Aktar" toggleAnnotations = "Açıklamaların Görünürlüğünü Değiştir" annotationMode = "Açıklama Modunu Değiştir" +print = "PDF'yi Yazdır" draw = "Çiz" save = "Kaydet" saveChanges = "Değişiklikleri Kaydet" @@ -4494,6 +4602,7 @@ description = "Impressum için URL veya dosya adı (bazı yargı bölgelerinde g title = "Premium ve Kurumsal" description = "Premium veya kurumsal lisans anahtarınızı yapılandırın." license = "Lisans Yapılandırması" +noInput = "Lütfen bir lisans anahtarı veya dosyası sağlayın" [admin.settings.premium.licenseKey] toggle = "Lisans anahtarınız veya sertifika dosyanız mı var?" @@ -4511,6 +4620,25 @@ line1 = "Mevcut lisans anahtarınızın üzerine yazma işlemi geri alınamaz." line2 = "Başka yerde yedeğiniz yoksa önceki lisansınız kalıcı olarak kaybolacaktır." line3 = "Önemli: Lisans anahtarlarını gizli ve güvenli tutun. Asla herkese açık şekilde paylaşmayın." +[admin.settings.premium.inputMethod] +text = "Lisans Anahtarı" +file = "Sertifika Dosyası" + +[admin.settings.premium.file] +label = "Lisans Sertifika Dosyası" +description = "Çevrimdışı satın alımlardan aldığınız .lic veya .cert lisans dosyanızı yükleyin" +choose = "Lisans Dosyası Seç" +selected = "Seçildi: {{filename}} ({{size}})" +successMessage = "Lisans dosyası başarıyla yüklendi ve etkinleştirildi. Yeniden başlatma gerekmez." + +[admin.settings.premium.currentLicense] +title = "Etkin Lisans" +file = "Kaynak: Lisans dosyası ({{path}})" +key = "Kaynak: Lisans anahtarı" +type = "Tür: {{type}}" +noInput = "Lütfen bir lisans anahtarı girin veya bir sertifika dosyası yükleyin" +success = "Başarılı" + [admin.settings.premium.enabled] label = "Premium Özellikleri Etkinleştir" description = "Pro/kurumsal özellikler için lisans anahtarı kontrollerini etkinleştir" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} seçildi" download = "İndir" delete = "Sil" unsupported = "Desteklenmiyor" +active = "Aktif" addToUpload = "Yüklemeye Ekle" +closeFile = "Dosyayı Kapat" deleteAll = "Tümünü Sil" loadingFiles = "Dosyalar yükleniyor..." noFiles = "Kullanılabilir dosya yok" @@ -5132,7 +5262,7 @@ upgrade = "Şimdi yükselt →" freeTitle = "Sunucu Lisansı" overLimitTitle = "Sunucu Lisansı Gerekli" overLimitBody = "Lisansımız, sunucu başına ücretsiz olarak en fazla {{freeTierLimit}} kullanıcıya izin verir. {{overLimitUserCopy}} Stirling kullanıcınız var. Kesintisiz devam etmek için Stirling Server planına yükseltin - sınırsız koltuk, PDF metin düzenleme ve tam yönetici kontrolü $99/server/ay." -freeBody = "Open-Core lisansımız, sunucu başına ücretsiz olarak en fazla {{freeTierLimit}} kullanıcıya izin verir. Kesintisiz ölçeklemek ve yeni PDF metin düzenleme aracımıza erken erişim almak için Stirling Server planını öneririz - tam düzenleme ve sınırsız koltuk $99/server/ay." +freeBody = "Open-Core lisanslamamız, sunucu başına en fazla {{freeTierLimit}} kullanıcıya ücretsiz izin verir. Kesintisiz ölçeklendirme için Stirling Server planını öneririz - sınırsız kullanıcı ve SSO desteği için $99/sunucu/ay." [onboarding.desktopInstall] title = "İndir" @@ -5237,6 +5367,31 @@ error = "Kullanıcı durumu güncellenemedi" success = "Kullanıcı başarıyla silindi" error = "Kullanıcı silinemedi" +[workspace.people.changePassword] +action = "Parolayı değiştir" +title = "Parolayı değiştir" +subtitle = "Şunun için parolayı güncelle" +newPassword = "Yeni parola" +confirmPassword = "Parolayı onayla" +placeholder = "Yeni bir parola girin" +confirmPlaceholder = "Yeni parolayı tekrar girin" +passwordRequired = "Lütfen yeni bir parola girin" +passwordMismatch = "Parolalar eşleşmiyor" +generateRandom = "Güvenli parola oluştur" +generatedPreview = "Oluşturulan parola:" +copyTooltip = "Panoya kopyala" +copiedToClipboard = "Parola panoya kopyalandı" +copyFailed = "Parola kopyalanamadı" +sendEmail = "Kullanıcıya bu değişiklik hakkında e-posta gönder" +includePassword = "E-postaya yeni parolayı dahil et" +forcePasswordChange = "Kullanıcının bir sonraki girişte parolasını değiştirmesini zorunlu kıl" +emailUnavailable = "Bu kullanıcının e-posta adresi geçerli değil. Bildirimler devre dışı." +smtpDisabled = "E-posta bildirimleri için ayarlarda SMTP'nin etkinleştirilmesi gerekir." +notifyOnly = "Parola olmadan bir e-posta gönderilecek ve kullanıcıya bir yöneticinin parolayı değiştirdiği bildirilecek." +submit = "Parolayı güncelle" +success = "Parola başarıyla güncellendi" +error = "Parola güncellenemedi" + [workspace.people.emailInvite] tab = "E-posta Daveti" description = "Aşağıya e-postaları virgülle ayırarak yazın veya yapıştırın. Kullanıcılar giriş bilgilerini e-posta ile alacaktır." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "En az bir e-posta adresi gereklidir" submit = "Davetleri Gönder" success = "kullanıcı(lar) başarıyla davet edildi" -partialSuccess = "Bazı davetler başarısız oldu" +partialFailure = "Bazı davetler başarısız oldu" allFailed = "Kullanıcılar davet edilemedi" error = "Davetler gönderilemedi" @@ -5770,6 +5925,7 @@ subtitle = "Stirling hesabınızla oturum açın" [setup.selfhosted] title = "Sunucuda Oturum Açın" subtitle = "Sunucu kimlik bilgilerinizi girin" +link = "veya kendi barındırdığınız bir hesaba bağlanın" [setup.server] title = "Sunucuya Bağlan" @@ -5788,6 +5944,14 @@ description = "Öz barındırılan Stirling PDF sunucunuzun tam URL'sini girin" emptyUrl = "Lütfen bir sunucu URL'si girin" unreachable = "Sunucuya bağlanılamadı" testFailed = "Bağlantı testi başarısız" +configFetch = "Sunucu yapılandırması alınamadı. Lütfen URL'yi kontrol edip tekrar deneyin." + +[setup.server.error.securityDisabled] +title = "Giriş Etkin Değil" +body = "Bu sunucuda giriş etkin değil. Bu sunucuya bağlanmak için kimlik doğrulamayı etkinleştirmelisiniz:" +step1 = "Ortamınızda DOCKER_ENABLE_SECURITY=true olarak ayarlayın" +step2 = "Ya da settings.yml içinde security.enableLogin=true olarak ayarlayın" +step3 = "Sunucuyu yeniden başlatın" [setup.login] title = "Oturum Aç" @@ -5797,6 +5961,13 @@ submit = "Oturum Aç" signInWith = "Şununla oturum aç" oauthPending = "Kimlik doğrulama için tarayıcı açılıyor..." orContinueWith = "Veya e-postayla devam edin" +serverRequirement = "Not: Sunucuda oturum açma etkin olmalıdır." +showInstructions = "Nasıl etkinleştirilir?" +hideInstructions = "Yönergeleri gizle" +instructions = "Stirling PDF sunucunuzda oturum açmayı etkinleştirmek için:" +instructionsEnvVar = "Ortam değişkenini ayarlayın:" +instructionsOrYml = "Veya settings.yml içinde:" +instructionsRestart = "Ardından değişikliklerin etkili olması için sunucunuzu yeniden başlatın." [setup.login.username] label = "Kullanıcı adı" @@ -5853,6 +6024,7 @@ earlyAccess = "Erken Erişim" reset = "Değişiklikleri Sıfırla" downloadJson = "JSON'u İndir" generatePdf = "PDF Oluştur" +saveChanges = "Değişiklikleri Kaydet" [pdfTextEditor.options.autoScaleText] title = "Metni kutulara otomatik sığdır" @@ -5890,6 +6062,8 @@ alpha = "Bu alfa görüntüleyici hâlâ gelişiyor—bazı yazı tipleri, renkl [pdfTextEditor.empty] title = "Belge yüklenmedi" subtitle = "Metin içeriğini düzenlemeye başlamak için bir PDF veya JSON dosyası yükleyin." +dropzone = "Buraya bir PDF veya JSON dosyası sürükleyip bırakın veya göz atmak için tıklayın" +dropzoneWithFiles = "Dosyalar sekmesinden bir dosya seçin veya buraya bir PDF veya JSON dosyası sürükleyip bırakın veya göz atmak için tıklayın" [pdfTextEditor.welcomeBanner] title = "PDF Metin Düzenleyiciye Hoş Geldiniz (Erken Erişim)" diff --git a/frontend/public/locales/uk-UA/translation.toml b/frontend/public/locales/uk-UA/translation.toml index dbb957a6a..7a21b1e65 100644 --- a/frontend/public/locales/uk-UA/translation.toml +++ b/frontend/public/locales/uk-UA/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Видалити з вибраного" fullscreen = "Переключитися на повноекранний режим" sidebar = "Переключитися на режим бічної панелі" +[backendStartup] +notFoundTitle = "Серверну частину не знайдено" +retry = "Повторити" +unreachable = "Застосунок наразі не може під’єднатися до серверної частини. Перевірте стан серверної частини та мережеве з’єднання, потім спробуйте ще раз." + [zipWarning] title = "Великий ZIP-файл" message = "Цей ZIP містить {{count}} файлів. Розпакувати попри це?" @@ -912,6 +917,9 @@ desc = "Створюйте багатокрокові робочі процес desc = "Накладення одного PDF поверх іншого PDF" title = "Накладення PDF" +[home.pdfTextEditor] +title = "Редактор тексту PDF" +desc = "Редагуйте наявний текст і зображення у PDF" [home.addText] tags = "текст,анотація,мітка" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Намальований підпис" defaultImageLabel = "Завантажений підпис" defaultTextLabel = "Набраний підпис" saveButton = "Зберегти підпис" +savePersonal = "Зберегти як особистий" +saveShared = "Зберегти як спільний" saveUnavailable = "Спочатку створіть підпис, щоб зберегти його." noChanges = "Поточний підпис вже збережено." +tempStorageTitle = "Тимчасове сховище браузера" +tempStorageDescription = "Підписи зберігаються лише у вашому браузері. Вони будуть втрачені, якщо ви очистите дані браузера або зміните браузер." +personalHeading = "Особисті підписи" +sharedHeading = "Спільні підписи" +personalDescription = "Лише ви можете бачити ці підписи." +sharedDescription = "Усі користувачі можуть бачити та використовувати ці підписи." [sign.saved.type] canvas = "Малювання" @@ -3020,6 +3036,91 @@ title = "Отримати інформацію в PDF" header = "Отримати інформацію в PDF" submit = "Отримати інформацію" downloadJson = "Завантажити JSON" +processing = "Вилучення інформації..." +results = "Результати" +noResults = "Запустіть інструмент, щоб сформувати звіт." +downloads = "Завантаження" +noneDetected = "Нічого не виявлено" +indexTitle = "Покажчик" + +[getPdfInfo.report] +entryLabel = "Повне зведення інформації" +shortTitle = "Інформація про PDF" + +[getPdfInfo.sections] +metadata = "Метадані" +formFields = "Поля форми" +basicInfo = "Базова інформація" +documentInfo = "Інформація про документ" +compliance = "Відповідність" +encryption = "Шифрування" +permissions = "Дозволи" +other = "Інше" +perPageInfo = "Інформація по сторінках" +tableOfContents = "Зміст" + +[getPdfInfo.other] +attachments = "Вкладення" +embeddedFiles = "Вбудовані файли" +javaScript = "JavaScript" +layers = "Шари" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Розмір" +annotations = "Анотації" +images = "Зображення" +links = "Посилання" +fonts = "Шрифти" +xobjects = "Кількість XObject" +multimedia = "Мультимедіа" + +[getPdfInfo.summary] +pages = "Сторінки" +fileSize = "Розмір файлу" +pdfVersion = "Версія PDF" +language = "Мова" +title = "Зведення PDF" +author = "Автор" +created = "Створено" +modified = "Змінено" +permsAll = "Усі дозволи надано" +permsRestricted = "{{count}} обмежень" +permsMixed = "Деякі дозволи обмежено" +hasCompliance = "Відповідає стандартам відповідності" +noCompliance = "Немає стандартів відповідності" +basic = "Основна інформація" +documentInfo = "Інформація про документ" +securityTitle = "Стан безпеки" +technical = "Технічна інформація" +overviewTitle = "Огляд PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF зашифровано — встановлено захист паролем" +unencrypted = "PDF не зашифровано — немає захисту паролем" + +[getPdfInfo.summary.tech] +images = "Зображення" +fonts = "Шрифти" +formFields = "Поля форми" +embeddedFiles = "Вбудовані файли" +javaScript = "JavaScript" +layers = "Шари" +bookmarks = "Закладки" +multimedia = "Мультимедіа" + +[getPdfInfo.summary.overview] +untitled = "документ без назви" +unknown = "Невідомий автор" +text = "Це PDF на {{pages}} сторінок під назвою {{title}}, створений {{author}} (версія PDF {{version}})." + +[getPdfInfo.error] +partial = "Деякі файли не вдалося обробити." +unexpected = "Неочікувана помилка під час вилучення." + +[getPdfInfo.status] +complete = "Вилучення завершено" [extractPage] tags = "екстракт" @@ -3438,6 +3539,9 @@ signinTitle = "Будь ласка, увійдіть" ssoSignIn = "Увійти через єдиний вхід" oAuth2AutoCreateDisabled = "Автоматичне створення користувача OAUTH2 ВИМКНЕНО" oAuth2AdminBlockedUser = "Реєстрація або вхід незареєстрованих користувачів наразі заборонено. Будь ласка, зв'яжіться з адміністратором." +oAuth2RequiresLicense = "Вхід через OAuth/SSO потребує платної ліцензії (Server або Enterprise). Зверніться до адміністратора, щоб оновити ваш план." +saml2RequiresLicense = "Вхід через SAML потребує платної ліцензії (Server або Enterprise). Зверніться до адміністратора, щоб оновити ваш план." +maxUsersReached = "Досягнуто максимальної кількості користувачів для вашої поточної ліцензії. Зверніться до адміністратора, щоб оновити план або додати місця." oauth2RequestNotFound = "Запит на авторизація не знайдено" oauth2InvalidUserInfoResponse = "Недійсна відповідь з інформацією користувача" oauth2invalidRequest = "Недійсний запит" @@ -3771,7 +3875,7 @@ version = "Текущий релиз" title = "Документація API" header = "Документація API" desc = "Переглядайте та тестуйте кінцеві точки API Stirling PDF" -tags = "api,documentation,swagger,endpoints,development" +tags = "api,документація,swagger,кінцеві точки,розробка" [cookieBanner.popUp] title = "Як ми використовуємо файли cookie" @@ -3846,14 +3950,17 @@ fitToWidth = "Підігнати за шириною" actualSize = "Фактичний розмір" [viewer] +cannotPreviewFile = "Не вдається переглянути файл" +dualPageView = "Парний перегляд" firstPage = "Перша сторінка" lastPage = "Остання сторінка" -previousPage = "Попередня сторінка" nextPage = "Наступна сторінка" +onlyPdfSupported = "Переглядач підтримує лише файли PDF. Схоже, цей файл має інший формат." +previousPage = "Попередня сторінка" +singlePageView = "Одинарний перегляд" +unknownFile = "Невідомий файл" zoomIn = "Збільшити" zoomOut = "Зменшити" -singlePageView = "Одинарний перегляд" -dualPageView = "Парний перегляд" [rightRail] closeSelected = "Закрити вибрані файли" @@ -3877,6 +3984,7 @@ toggleSidebar = "Перемкнути бічну панель" exportSelected = "Експорт вибраних сторінок" toggleAnnotations = "Перемкнути видимість анотацій" annotationMode = "Перемкнути режим анотацій" +print = "Надрукувати PDF" draw = "Малювати" save = "Зберегти" saveChanges = "Зберегти зміни" @@ -4153,7 +4261,7 @@ description = "Відстежувати дії користувачів і си [admin.settings.security.audit.level] label = "Рівень аудиту" -description = "0=OFF, 1=BASIC, 2=STANDARD, 3=VERBOSE" +description = "0=ВИМКНЕНО, 1=БАЗОВИЙ, 2=СТАНДАРТ, 3=ДОКЛАДНИЙ" [admin.settings.security.audit.retentionDays] label = "Зберігання аудиту (дні)" @@ -4407,7 +4515,7 @@ description = "Чи очищати ширший системний тимчас label = "Обмеження виконавця процесів" description = "Налаштуйте ліміти сеансів і тайм-аути для кожного виконавця процесів" libreOffice = "LibreOffice" -pdfToHtml = "PDF to HTML" +pdfToHtml = "PDF у HTML" qpdf = "QPDF" tesseract = "Tesseract OCR" pythonOpenCv = "Python OpenCV" @@ -4494,6 +4602,7 @@ description = "URL або назва файлу до імпресуму (пот title = "Преміум і Enterprise" description = "Налаштуйте свій преміум або корпоративний ліцензійний ключ." license = "Конфігурація ліцензії" +noInput = "Надайте ліцензійний ключ або файл" [admin.settings.premium.licenseKey] toggle = "Є ліцензійний ключ або файл сертифіката?" @@ -4511,6 +4620,25 @@ line1 = "Перезапис поточного ліцензійного ключ line2 = "Попередня ліцензія буде втрачена назавжди, якщо ви не маєте її резервної копії." line3 = "Важливо: зберігайте ліцензійні ключі приватними та безпечними. Ніколи не публікуйте їх." +[admin.settings.premium.inputMethod] +text = "Ліцензійний ключ" +file = "Файл сертифіката" + +[admin.settings.premium.file] +label = "Файл ліцензійного сертифіката" +description = "Завантажте свій ліцензійний файл .lic або .cert з офлайн-покупок" +choose = "Виберіть ліцензійний файл" +selected = "Вибрано: {{filename}} ({{size}})" +successMessage = "Ліцензійний файл успішно завантажено й активовано. Перезапуск не потрібен." + +[admin.settings.premium.currentLicense] +title = "Активна ліцензія" +file = "Джерело: ліцензійний файл ({{path}})" +key = "Джерело: ліцензійний ключ" +type = "Тип: {{type}}" +noInput = "Надайте ліцензійний ключ або завантажте файл сертифіката" +success = "Успішно" + [admin.settings.premium.enabled] label = "Увімкнути преміум-функції" description = "Увімкнути перевірку ліцензійного ключа для pro/enterprise функцій" @@ -4644,7 +4772,9 @@ selectedCount = "Вибрано {{count}}" download = "Завантажити" delete = "Видалити" unsupported = "Непідтримуваний" +active = "Активний" addToUpload = "Додати до завантаження" +closeFile = "Закрити файл" deleteAll = "Видалити все" loadingFiles = "Завантаження файлів..." noFiles = "Немає доступних файлів" @@ -5132,7 +5262,7 @@ upgrade = "Оновити зараз →" freeTitle = "Ліцензія сервера" overLimitTitle = "Потрібна ліцензія сервера" overLimitBody = "Наша ліцензія дозволяє до {{freeTierLimit}} користувачів безкоштовно на сервер. У вас {{overLimitUserCopy}} користувачів Stirling. Щоб працювати без перерв, перейдіть на план Stirling Server — необмежена кількість місць, редагування тексту PDF та повний адмін-контроль за $99/server/mo." -freeBody = "Наша Open-Core ліцензія дозволяє до {{freeTierLimit}} користувачів безкоштовно на сервер. Щоб масштабуватися безперервно та отримати ранній доступ до нового інструмента редагування тексту PDF, рекомендуємо план Stirling Server — повне редагування та необмежена кількість місць за $99/server/mo." +freeBody = "Наша ліцензія Open-Core дозволяє до {{freeTierLimit}} користувачів безкоштовно на сервер. Щоб масштабуватися без перерв, рекомендуємо план Stirling Server — необмежена кількість місць і підтримка SSO за $99/сервер/міс." [onboarding.desktopInstall] title = "Завантажити" @@ -5237,6 +5367,31 @@ error = "Не вдалося оновити статус користувача" success = "Користувача успішно видалено" error = "Не вдалося видалити користувача" +[workspace.people.changePassword] +action = "Змінити пароль" +title = "Змінити пароль" +subtitle = "Оновити пароль для" +newPassword = "Новий пароль" +confirmPassword = "Підтвердження пароля" +placeholder = "Введіть новий пароль" +confirmPlaceholder = "Повторно введіть новий пароль" +passwordRequired = "Будь ласка, введіть новий пароль" +passwordMismatch = "Паролі не збігаються" +generateRandom = "Згенерувати надійний пароль" +generatedPreview = "Згенерований пароль:" +copyTooltip = "Копіювати в буфер обміну" +copiedToClipboard = "Пароль скопійовано в буфер обміну" +copyFailed = "Не вдалося скопіювати пароль" +sendEmail = "Надіслати користувачу електронний лист про цю зміну" +includePassword = "Додати новий пароль до листа" +forcePasswordChange = "Примусити користувача змінити пароль під час наступного входу" +emailUnavailable = "Електронна адреса цього користувача недійсна. Сповіщення вимкнено." +smtpDisabled = "Для сповіщень електронною поштою потрібно ввімкнути SMTP у налаштуваннях." +notifyOnly = "Буде надіслано лист без пароля, щоб повідомити користувача, що адміністратор його змінив." +submit = "Оновити пароль" +success = "Пароль успішно оновлено" +error = "Не вдалося оновити пароль" + [workspace.people.emailInvite] tab = "Запрошення електронною поштою" description = "Введіть або вставте адреси нижче, розділені комами. Користувачі отримають дані для входу електронною поштою." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Потрібна щонайменше одна електронна адреса" submit = "Надіслати запрошення" success = "Користувача(ів) успішно запрошено" -partialSuccess = "Деякі запрошення не вдалося надіслати" +partialFailure = "Деякі запрошення не вдалося надіслати" allFailed = "Не вдалося запросити користувачів" error = "Не вдалося надіслати запрошення" @@ -5281,7 +5436,7 @@ submit = "Згенерувати посилання-запрошення" [workspace.people.inviteMode] username = "Ім’я користувача" -email = "Email" +email = "Електронна пошта" link = "Посилання" emailDisabled = "Запрошення електронною поштою потребують налаштування SMTP і mail.enableInvites=true в налаштуваннях" @@ -5770,6 +5925,7 @@ subtitle = "Увійдіть через обліковий запис Stirling" [setup.selfhosted] title = "Увійдіть на сервер" subtitle = "Введіть облікові дані сервера" +link = "або підключіться до самохостингового облікового запису" [setup.server] title = "Під’єднатися до сервера" @@ -5788,6 +5944,14 @@ description = "Введіть повну URL-адресу вашого само emptyUrl = "Введіть URL сервера" unreachable = "Не вдалося під’єднатися до сервера" testFailed = "Тест підключення не вдався" +configFetch = "Не вдалося отримати конфігурацію сервера. Перевірте URL і спробуйте ще раз." + +[setup.server.error.securityDisabled] +title = "Вхід не ввімкнено" +body = "На цьому сервері не ввімкнено вхід. Щоб підключитися до цього сервера, потрібно ввімкнути автентифікацію:" +step1 = "Встановіть DOCKER_ENABLE_SECURITY=true у вашому середовищі" +step2 = "Або встановіть security.enableLogin=true у settings.yml" +step3 = "Перезапустіть сервер" [setup.login] title = "Вхід" @@ -5797,13 +5961,20 @@ submit = "Увійти" signInWith = "Увійти через" oauthPending = "Відкриваємо браузер для автентифікації..." orContinueWith = "Або продовжити через email" +serverRequirement = "Примітка: на сервері має бути увімкнено вхід." +showInstructions = "Як увімкнути?" +hideInstructions = "Приховати інструкції" +instructions = "Щоб увімкнути вхід на вашому сервері Stirling PDF:" +instructionsEnvVar = "Задайте змінну середовища:" +instructionsOrYml = "Або у settings.yml:" +instructionsRestart = "Потім перезапустіть сервер, щоб зміни набрали чинності." [setup.login.username] label = "Ім’я користувача" placeholder = "Введіть ім’я користувача" [setup.login.email] -label = "Email" +label = "Електронна пошта" placeholder = "Введіть свій email" [setup.login.password] @@ -5853,6 +6024,7 @@ earlyAccess = "Ранній доступ" reset = "Скинути зміни" downloadJson = "Завантажити JSON" generatePdf = "Згенерувати PDF" +saveChanges = "Зберегти зміни" [pdfTextEditor.options.autoScaleText] title = "Автопідгін тексту під рамки" @@ -5890,6 +6062,8 @@ alpha = "Цей альфа-переглядач ще розвивається [pdfTextEditor.empty] title = "Документ не завантажено" subtitle = "Завантажте файл PDF або JSON, щоб почати редагувати текстовий вміст." +dropzone = "Перетягніть і відпустіть файл PDF або JSON сюди, або натисніть, щоб вибрати" +dropzoneWithFiles = "Виберіть файл на вкладці Файли або перетягніть і відпустіть файл PDF чи JSON сюди, або натисніть, щоб вибрати" [pdfTextEditor.welcomeBanner] title = "Вітаємо у Редакторі тексту PDF (ранній доступ)" diff --git a/frontend/public/locales/vi-VN/translation.toml b/frontend/public/locales/vi-VN/translation.toml index 464407a2a..9b21be6ef 100644 --- a/frontend/public/locales/vi-VN/translation.toml +++ b/frontend/public/locales/vi-VN/translation.toml @@ -163,6 +163,11 @@ unfavorite = "Xóa khỏi Mục yêu thích" fullscreen = "Chuyển sang chế độ toàn màn hình" sidebar = "Chuyển sang chế độ thanh bên" +[backendStartup] +notFoundTitle = "Không tìm thấy Backend" +retry = "Thử lại" +unreachable = "Ứng dụng hiện không thể kết nối tới Backend. Hãy kiểm tra trạng thái Backend và kết nối mạng, sau đó thử lại." + [zipWarning] title = "Tệp ZIP lớn" message = "ZIP này chứa {{count}} tệp. Vẫn giải nén?" @@ -296,7 +301,7 @@ saveSettings = "Lưu cài đặt thao tác" pipelineNamePrompt = "Nhập tên pipeline tại đây" selectOperation = "Chọn thao tác" addOperationButton = "Thêm thao tác" -pipelineHeader = "Pipeline:" +pipelineHeader = "Chuỗi xử lý:" saveButton = "Tải xuống" validateButton = "Xác thực" @@ -347,7 +352,7 @@ teams = "Nhóm" title = "Cấu hình" systemSettings = "Cài đặt hệ thống" features = "Tính năng" -endpoints = "Endpoints" +endpoints = "Điểm cuối" database = "Cơ sở dữ liệu" advanced = "Nâng cao" @@ -359,7 +364,7 @@ connections = "Kết nối" [settings.licensingAnalytics] title = "Giấy phép & Phân tích" plan = "Gói" -audit = "Audit" +audit = "Kiểm toán" usageAnalytics = "Phân tích sử dụng" [settings.policiesPrivacy] @@ -369,7 +374,7 @@ privacy = "Quyền riêng tư" [settings.developer] title = "Nhà phát triển" -apiKeys = "API Keys" +apiKeys = "Khóa API" [settings.tooltips] enableLoginFirst = "Bật chế độ đăng nhập trước" @@ -912,6 +917,9 @@ desc = "Xây dựng quy trình nhiều bước bằng cách xâu chuỗi các th desc = "Chồng lớp PDF lên trên PDF khác" title = "Chồng lớp PDF" +[home.pdfTextEditor] +title = "Trình chỉnh sửa văn bản PDF" +desc = "Chỉnh sửa văn bản và hình ảnh hiện có bên trong PDF" [home.addText] tags = "văn bản,chú thích,nhãn" @@ -1173,7 +1181,7 @@ selectFilesPlaceholder = "Chọn tệp trong khung chính để bắt đầu" settings = "Cài đặt" conversionCompleted = "Hoàn tất chuyển đổi" results = "Kết quả" -defaultFilename = "converted_file" +defaultFilename = "tep_da_chuyen_doi" conversionResults = "Kết quả chuyển đổi" convertFrom = "Chuyển từ" convertTo = "Sang" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "Chữ ký vẽ" defaultImageLabel = "Chữ ký đã tải lên" defaultTextLabel = "Chữ ký nhập" saveButton = "Lưu chữ ký" +savePersonal = "Lưu cá nhân" +saveShared = "Lưu dùng chung" saveUnavailable = "Hãy tạo chữ ký trước để lưu." noChanges = "Chữ ký hiện tại đã được lưu." +tempStorageTitle = "Bộ nhớ tạm của trình duyệt" +tempStorageDescription = "Chữ ký chỉ được lưu trong trình duyệt của bạn. Chúng sẽ bị mất nếu bạn xóa dữ liệu trình duyệt hoặc đổi sang trình duyệt khác." +personalHeading = "Chữ ký cá nhân" +sharedHeading = "Chữ ký dùng chung" +personalDescription = "Chỉ mình bạn có thể xem các chữ ký này." +sharedDescription = "Tất cả người dùng đều có thể xem và sử dụng các chữ ký này." [sign.saved.type] canvas = "Vẽ" @@ -2567,7 +2583,7 @@ stopButton = "Dừng so sánh" [certSign] tags = "xác thực,PEM,P12,chính thức,mã hóa" title = "Ký bằng chứng chỉ" -filenamePrefix = "signed" +filenamePrefix = "da_ky" chooseCertificate = "Chọn tệp chứng chỉ" chooseJksFile = "Chọn tệp JKS" chooseP12File = "Chọn tệp PKCS12" @@ -2701,7 +2717,7 @@ header = "Xóa chứng chỉ số khỏi PDF" selectPDF = "Chọn một tệp PDF:" submit = "Xóa chữ ký" description = "Công cụ này sẽ xóa chữ ký chứng chỉ số khỏi tài liệu PDF của bạn." -filenamePrefix = "unsigned" +filenamePrefix = "chua_ky" [removeCertSign.files] placeholder = "Chọn một tệp PDF trong màn hình chính để bắt đầu" @@ -3020,6 +3036,91 @@ title = "Lấy thông tin PDF" header = "Lấy thông tin PDF" submit = "Lấy thông tin" downloadJson = "Tải xuống JSON" +processing = "Đang trích xuất thông tin..." +results = "Kết quả" +noResults = "Chạy công cụ để tạo báo cáo." +downloads = "Tải xuống" +noneDetected = "Không phát hiện" +indexTitle = "Chỉ mục" + +[getPdfInfo.report] +entryLabel = "Tóm tắt thông tin đầy đủ" +shortTitle = "Thông tin PDF" + +[getPdfInfo.sections] +metadata = "Siêu dữ liệu" +formFields = "Trường biểu mẫu" +basicInfo = "Thông tin cơ bản" +documentInfo = "Thông tin tài liệu" +compliance = "Tuân thủ" +encryption = "Mã hóa" +permissions = "Quyền" +other = "Khác" +perPageInfo = "Thông tin theo trang" +tableOfContents = "Mục lục" + +[getPdfInfo.other] +attachments = "Tệp đính kèm" +embeddedFiles = "Tệp nhúng" +javaScript = "JavaScript" +layers = "Lớp" +structureTree = "StructureTree" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "Kích thước" +annotations = "Chú thích" +images = "Hình ảnh" +links = "Liên kết" +fonts = "Phông chữ" +xobjects = "Số lượng XObject" +multimedia = "Đa phương tiện" + +[getPdfInfo.summary] +pages = "Trang" +fileSize = "Kích thước tệp" +pdfVersion = "Phiên bản PDF" +language = "Ngôn ngữ" +title = "Tóm tắt PDF" +author = "Tác giả" +created = "Đã tạo" +modified = "Đã sửa đổi" +permsAll = "Tất cả quyền được cho phép" +permsRestricted = "{{count}} giới hạn" +permsMixed = "Một số quyền bị hạn chế" +hasCompliance = "Có tiêu chuẩn tuân thủ" +noCompliance = "Không có tiêu chuẩn tuân thủ" +basic = "Thông tin cơ bản" +documentInfo = "Thông tin tài liệu" +securityTitle = "Trạng thái bảo mật" +technical = "Kỹ thuật" +overviewTitle = "Tổng quan PDF" + +[getPdfInfo.summary.security] +encrypted = "PDF đã mã hóa - Có bảo vệ bằng mật khẩu" +unencrypted = "PDF không mã hóa - Không có bảo vệ bằng mật khẩu" + +[getPdfInfo.summary.tech] +images = "Hình ảnh" +fonts = "Phông chữ" +formFields = "Trường biểu mẫu" +embeddedFiles = "Tệp nhúng" +javaScript = "JavaScript" +layers = "Lớp" +bookmarks = "Dấu trang" +multimedia = "Đa phương tiện" + +[getPdfInfo.summary.overview] +untitled = "một tài liệu không tiêu đề" +unknown = "Tác giả không xác định" +text = "Đây là PDF {{pages}} trang có tiêu đề {{title}} do {{author}} tạo (phiên bản PDF {{version}})." + +[getPdfInfo.error] +partial = "Một số tệp không thể xử lý." +unexpected = "Lỗi không mong muốn trong quá trình trích xuất." + +[getPdfInfo.status] +complete = "Hoàn tất trích xuất" [extractPage] tags = "trích xuất" @@ -3438,6 +3539,9 @@ signinTitle = "Vui lòng đăng nhập" ssoSignIn = "Đăng nhập qua Single Sign-on" oAuth2AutoCreateDisabled = "Tự động tạo người dùng OAUTH2 bị vô hiệu hóa" oAuth2AdminBlockedUser = "Hiện đang chặn đăng ký hoặc đăng nhập người dùng chưa đăng ký. Vui lòng liên hệ quản trị viên." +oAuth2RequiresLicense = "Đăng nhập OAuth/SSO cần giấy phép trả phí (Server hoặc Enterprise). Vui lòng liên hệ quản trị viên để nâng cấp gói của bạn." +saml2RequiresLicense = "Đăng nhập SAML cần giấy phép trả phí (Server hoặc Enterprise). Vui lòng liên hệ quản trị viên để nâng cấp gói của bạn." +maxUsersReached = "Đã đạt số lượng người dùng tối đa cho giấy phép hiện tại của bạn. Vui lòng liên hệ quản trị viên để nâng cấp gói hoặc thêm suất." oauth2RequestNotFound = "Không tìm thấy yêu cầu ủy quyền" oauth2InvalidUserInfoResponse = "Phản hồi thông tin người dùng không hợp lệ" oauth2invalidRequest = "Yêu cầu không hợp lệ" @@ -3533,7 +3637,7 @@ title = "PDF thành một trang" header = "PDF thành một trang" submit = "Chuyển đổi thành một trang" description = "Công cụ này sẽ gộp tất cả các trang của PDF của bạn thành một trang đơn lớn. Chiều rộng giữ nguyên như các trang gốc, còn chiều cao sẽ bằng tổng chiều cao của tất cả các trang." -filenamePrefix = "single_page" +filenamePrefix = "mot_trang" [pdfToSinglePage.files] placeholder = "Chọn một tệp PDF ở màn hình chính để bắt đầu" @@ -3846,14 +3950,17 @@ fitToWidth = "Vừa chiều rộng" actualSize = "Kích thước thật" [viewer] +cannotPreviewFile = "Không thể xem trước tệp" +dualPageView = "Chế độ xem hai trang" firstPage = "Trang đầu" lastPage = "Trang cuối" -previousPage = "Trang trước" nextPage = "Trang tiếp" +onlyPdfSupported = "Trình xem chỉ hỗ trợ tệp PDF. Tệp này có vẻ là định dạng khác." +previousPage = "Trang trước" +singlePageView = "Chế độ xem trang đơn" +unknownFile = "Tệp không xác định" zoomIn = "Phóng to" zoomOut = "Thu nhỏ" -singlePageView = "Chế độ xem trang đơn" -dualPageView = "Chế độ xem hai trang" [rightRail] closeSelected = "Đóng các tệp đã chọn" @@ -3877,6 +3984,7 @@ toggleSidebar = "Chuyển đổi thanh bên" exportSelected = "Xuất các trang đã chọn" toggleAnnotations = "Chuyển đổi hiển thị chú thích" annotationMode = "Chuyển đổi chế độ chú thích" +print = "In PDF" draw = "Vẽ" save = "Lưu" saveChanges = "Lưu thay đổi" @@ -4494,6 +4602,7 @@ description = "URL hoặc tên tệp cho impressum (bắt buộc ở một số title = "Premium & Enterprise" description = "Cấu hình khóa giấy phép premium hoặc enterprise của bạn." license = "Cấu hình giấy phép" +noInput = "Vui lòng cung cấp khóa giấy phép hoặc tệp" [admin.settings.premium.licenseKey] toggle = "Có license key hoặc tệp chứng chỉ?" @@ -4511,6 +4620,25 @@ line1 = "Ghi đè license key hiện tại không thể hoàn tác." line2 = "License trước đó sẽ bị mất vĩnh viễn trừ khi bạn đã sao lưu ở nơi khác." line3 = "Quan trọng: Giữ license key riêng tư và an toàn. Không bao giờ chia sẻ công khai." +[admin.settings.premium.inputMethod] +text = "Khóa giấy phép" +file = "Tệp chứng chỉ" + +[admin.settings.premium.file] +label = "Tệp chứng chỉ giấy phép" +description = "Tải lên tệp giấy phép .lic hoặc .cert từ các lần mua ngoại tuyến của bạn" +choose = "Chọn tệp giấy phép" +selected = "Đã chọn: {{filename}} ({{size}})" +successMessage = "Tệp giấy phép đã được tải lên và kích hoạt thành công. Không cần khởi động lại." + +[admin.settings.premium.currentLicense] +title = "Giấy phép đang hoạt động" +file = "Nguồn: Tệp giấy phép ({{path}})" +key = "Nguồn: Khóa giấy phép" +type = "Loại: {{type}}" +noInput = "Vui lòng cung cấp khóa giấy phép hoặc tải lên tệp chứng chỉ" +success = "Thành công" + [admin.settings.premium.enabled] label = "Bật tính năng Premium" description = "Bật kiểm tra khóa giấy phép cho các tính năng pro/enterprise" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} đã chọn" download = "Tải xuống" delete = "Xóa" unsupported = "Không được hỗ trợ" +active = "Hoạt động" addToUpload = "Thêm vào tải lên" +closeFile = "Đóng tệp" deleteAll = "Xóa tất cả" loadingFiles = "Đang tải tệp..." noFiles = "Không có tệp nào" @@ -5132,7 +5262,7 @@ upgrade = "Nâng cấp ngay →" freeTitle = "Giấy phép Server" overLimitTitle = "Cần giấy phép Server" overLimitBody = "Giấy phép của chúng tôi cho phép tối đa {{freeTierLimit}} người dùng miễn phí mỗi server. Bạn có {{overLimitUserCopy}} người dùng Stirling. Để tiếp tục không gián đoạn, hãy nâng cấp lên gói Stirling Server - số ghế không giới hạn, chỉnh sửa văn bản PDF và toàn quyền quản trị với $99/server/tháng." -freeBody = "Giấy phép Open-Core của chúng tôi cho phép tối đa {{freeTierLimit}} người dùng miễn phí mỗi server. Để mở rộng không gián đoạn và truy cập sớm công cụ chỉnh sửa văn bản PDF mới, chúng tôi khuyến nghị gói Stirling Server - chỉnh sửa đầy đủ và số ghế không giới hạn với $99/server/tháng." +freeBody = "Giấy phép Open-Core của chúng tôi cho phép tối đa {{freeTierLimit}} người dùng miễn phí cho mỗi máy chủ. Để mở rộng quy mô liền mạch, chúng tôi khuyến nghị gói Stirling Server - số lượng người dùng không giới hạnhỗ trợ SSO với giá $99/máy chủ/tháng." [onboarding.desktopInstall] title = "Tải xuống" @@ -5237,6 +5367,31 @@ error = "Cập nhật trạng thái người dùng thất bại" success = "Xóa người dùng thành công" error = "Xóa người dùng thất bại" +[workspace.people.changePassword] +action = "Đổi mật khẩu" +title = "Đổi mật khẩu" +subtitle = "Cập nhật mật khẩu cho" +newPassword = "Mật khẩu mới" +confirmPassword = "Xác nhận mật khẩu" +placeholder = "Nhập mật khẩu mới" +confirmPlaceholder = "Nhập lại mật khẩu mới" +passwordRequired = "Vui lòng nhập mật khẩu mới" +passwordMismatch = "Mật khẩu không khớp" +generateRandom = "Tạo mật khẩu an toàn" +generatedPreview = "Mật khẩu đã tạo:" +copyTooltip = "Sao chép vào bộ nhớ tạm" +copiedToClipboard = "Đã sao chép mật khẩu vào bộ nhớ tạm" +copyFailed = "Không thể sao chép mật khẩu" +sendEmail = "Gửi email cho người dùng về thay đổi này" +includePassword = "Bao gồm mật khẩu mới trong email" +forcePasswordChange = "Buộc người dùng đổi mật khẩu ở lần đăng nhập tiếp theo" +emailUnavailable = "Email của người dùng này không phải là địa chỉ email hợp lệ. Đã tắt thông báo." +smtpDisabled = "Thông báo email yêu cầu bật SMTP trong cài đặt." +notifyOnly = "Sẽ gửi một email không kèm mật khẩu, cho người dùng biết quản trị viên đã thay đổi mật khẩu." +submit = "Cập nhật mật khẩu" +success = "Cập nhật mật khẩu thành công" +error = "Không thể cập nhật mật khẩu" + [workspace.people.emailInvite] tab = "Mời qua Email" description = "Nhập hoặc dán email bên dưới, phân tách bằng dấu phẩy. Người dùng sẽ nhận thông tin đăng nhập qua email." @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "Yêu cầu ít nhất một địa chỉ email" submit = "Gửi lời mời" success = "Mời người dùng thành công" -partialSuccess = "Một số lời mời thất bại" +partialFailure = "Một số lời mời không thành công" allFailed = "Mời người dùng thất bại" error = "Gửi lời mời thất bại" @@ -5770,6 +5925,7 @@ subtitle = "Đăng nhập bằng tài khoản Stirling" [setup.selfhosted] title = "Đăng nhập vào server" subtitle = "Nhập thông tin đăng nhập server" +link = "hoặc kết nối tới tài khoản tự lưu trữ" [setup.server] title = "Kết nối đến server" @@ -5788,6 +5944,14 @@ description = "Nhập URL đầy đủ của server Stirling PDF tự lưu trữ emptyUrl = "Vui lòng nhập URL server" unreachable = "Không thể kết nối đến server" testFailed = "Kiểm tra kết nối thất bại" +configFetch = "Không thể lấy cấu hình máy chủ. Vui lòng kiểm tra URL và thử lại." + +[setup.server.error.securityDisabled] +title = "Chưa bật đăng nhập" +body = "Máy chủ này chưa bật chức năng đăng nhập. Để kết nối tới máy chủ này, bạn phải bật xác thực:" +step1 = "Đặt DOCKER_ENABLE_SECURITY=true trong môi trường của bạn" +step2 = "Hoặc đặt security.enableLogin=true trong settings.yml" +step3 = "Khởi động lại máy chủ" [setup.login] title = "Đăng nhập" @@ -5797,6 +5961,13 @@ submit = "Đăng nhập" signInWith = "Đăng nhập với" oauthPending = "Đang mở trình duyệt để xác thực..." orContinueWith = "Hoặc tiếp tục bằng email" +serverRequirement = "Lưu ý: Máy chủ phải bật đăng nhập." +showInstructions = "Cách bật?" +hideInstructions = "Ẩn hướng dẫn" +instructions = "Để bật đăng nhập trên máy chủ Stirling PDF của bạn:" +instructionsEnvVar = "Đặt biến môi trường:" +instructionsOrYml = "Hoặc trong settings.yml:" +instructionsRestart = "Sau đó khởi động lại máy chủ để các thay đổi có hiệu lực." [setup.login.username] label = "Tên người dùng" @@ -5853,6 +6024,7 @@ earlyAccess = "Truy cập sớm" reset = "Đặt lại thay đổi" downloadJson = "Tải JSON" generatePdf = "Tạo PDF" +saveChanges = "Lưu thay đổi" [pdfTextEditor.options.autoScaleText] title = "Tự căn chỉnh văn bản cho vừa hộp" @@ -5890,6 +6062,8 @@ alpha = "Trình xem alpha này vẫn đang phát triển—một số font, màu [pdfTextEditor.empty] title = "Chưa tải tài liệu" subtitle = "Tải tệp PDF hoặc JSON để bắt đầu chỉnh sửa nội dung văn bản." +dropzone = "Kéo và thả tệp PDF hoặc JSON vào đây, hoặc nhấp để duyệt" +dropzoneWithFiles = "Chọn một tệp từ tab Tệp, hoặc kéo và thả tệp PDF hoặc JSON vào đây, hoặc nhấp để duyệt" [pdfTextEditor.welcomeBanner] title = "Chào mừng đến với PDF Text Editor (Truy cập sớm)" diff --git a/frontend/public/locales/zh-BO/translation.toml b/frontend/public/locales/zh-BO/translation.toml index efbc9cb91..8c05b4c5f 100644 --- a/frontend/public/locales/zh-BO/translation.toml +++ b/frontend/public/locales/zh-BO/translation.toml @@ -163,6 +163,11 @@ unfavorite = "从收藏中移除" fullscreen = "切换到全屏模式" sidebar = "切换到侧边栏模式" +[backendStartup] +notFoundTitle = "未找到后端" +retry = "重试" +unreachable = "应用目前无法连接到后端。请检查后端状态和网络连接,然后重试。" + [zipWarning] title = "大型 ZIP 文件" message = "此 ZIP 包含 {{count}} 个文件。仍要解压吗?" @@ -912,6 +917,9 @@ desc = "通过串联 PDF 动作构建多步工作流。适合重复性任务。" desc = "PDF གཞན་ཞིག་གི་སྟེང་དུ་ PDF བརྩེགས་པ།" title = "PDF སྟེང་བརྩེགས།" +[home.pdfTextEditor] +title = "PDF 文本编辑器" +desc = "编辑 PDF 中的现有文本和图像" [home.addText] tags = "文本,注释,标签" @@ -1173,7 +1181,7 @@ selectFilesPlaceholder = "在主视图中选择文件以开始" settings = "设置" conversionCompleted = "转换完成" results = "结果" -defaultFilename = "converted_file" +defaultFilename = "已转换_文件" conversionResults = "转换结果" convertFrom = "从以下格式转换" convertTo = "转换为" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "手写签名" defaultImageLabel = "已上传的签名" defaultTextLabel = "键入的签名" saveButton = "保存签名" +savePersonal = "保存为个人" +saveShared = "保存为共享" saveUnavailable = "请先创建签名再保存。" noChanges = "当前签名已保存。" +tempStorageTitle = "浏览器临时存储" +tempStorageDescription = "签名仅存储在您的浏览器中。若清除浏览器数据或更换浏览器,这些签名将会丢失。" +personalHeading = "个人签名" +sharedHeading = "共享签名" +personalDescription = "只有您可以看到这些签名。" +sharedDescription = "所有用户都可以查看并使用这些签名。" [sign.saved.type] canvas = "绘制" @@ -3020,6 +3036,91 @@ title = "PDF ཡི་གནས་ཚུལ་ལེན་པ།" header = "PDF ཡི་གནས་ཚུལ་ལེན་པ།" submit = "གནས་ཚུལ་ལེན་པ།" downloadJson = "JSON ཕབ་ལེན།" +processing = "正在提取信息..." +results = "结果" +noResults = "运行此工具以生成报告。" +downloads = "下载" +noneDetected = "未检测到" +indexTitle = "索引" + +[getPdfInfo.report] +entryLabel = "完整信息摘要" +shortTitle = "PDF 信息" + +[getPdfInfo.sections] +metadata = "元数据" +formFields = "表单字段" +basicInfo = "基本信息" +documentInfo = "文档信息" +compliance = "合规性" +encryption = "加密" +permissions = "权限" +other = "其他" +perPageInfo = "每页信息" +tableOfContents = "目录" + +[getPdfInfo.other] +attachments = "附件" +embeddedFiles = "嵌入文件" +javaScript = "JavaScript" +layers = "图层" +structureTree = "结构树" +xmp = "XMP 元数据" + +[getPdfInfo.perPage] +size = "尺寸" +annotations = "注释" +images = "图像" +links = "链接" +fonts = "字体" +xobjects = "XObject 数量" +multimedia = "多媒体" + +[getPdfInfo.summary] +pages = "页数" +fileSize = "文件大小" +pdfVersion = "PDF 版本" +language = "语言" +title = "PDF 摘要" +author = "作者" +created = "创建时间" +modified = "修改时间" +permsAll = "允许所有权限" +permsRestricted = "{{count}} 项限制" +permsMixed = "部分权限受限" +hasCompliance = "符合合规标准" +noCompliance = "无合规标准" +basic = "基本信息" +documentInfo = "文档信息" +securityTitle = "安全状态" +technical = "技术" +overviewTitle = "PDF 概览" + +[getPdfInfo.summary.security] +encrypted = "已加密的 PDF - 受密码保护" +unencrypted = "未加密的 PDF - 无密码保护" + +[getPdfInfo.summary.tech] +images = "图像" +fonts = "字体" +formFields = "表单字段" +embeddedFiles = "嵌入文件" +javaScript = "JavaScript" +layers = "图层" +bookmarks = "书签" +multimedia = "多媒体" + +[getPdfInfo.summary.overview] +untitled = "未命名文档" +unknown = "作者未知" +text = "这是一个 {{pages}} 页的 PDF,标题为 {{title}},由 {{author}} 创建(PDF 版本 {{version}})。" + +[getPdfInfo.error] +partial = "部分文件无法处理。" +unexpected = "提取过程中发生意外错误。" + +[getPdfInfo.status] +complete = "提取完成" [extractPage] tags = "ཕྱིར་འདོན།" @@ -3438,6 +3539,9 @@ signinTitle = "ནང་འཛུལ་གནང་རོགས།" ssoSignIn = "གཅིག་གྱུར་ནང་འཛུལ་བརྒྱུད་ནས་ནང་འཛུལ།" oAuth2AutoCreateDisabled = "OAUTH2 རང་འགུལ་སྤྱོད་མཁན་གསར་བཟོ་བཀག་སྡོམ་བྱས་ཟིན།" oAuth2AdminBlockedUser = "ད་ལྟ་ཐོ་འགོད་མ་བྱས་པའི་སྤྱོད་མཁན་གྱི་ཐོ་འགོད་དང་ནང་འཛུལ་བཀག་སྡོམ་བྱས་ཡོད། དོ་དམ་པར་འབྲེལ་བ་གནང་རོགས།" +oAuth2RequiresLicense = "使用 OAuth/SSO 登录需要付费许可(Server 或 Enterprise)。请联系管理员升级您的方案。" +saml2RequiresLicense = "使用 SAML 登录需要付费许可(Server 或 Enterprise)。请联系管理员升级您的方案。" +maxUsersReached = "您当前的许可已达到最大用户数。请联系管理员升级您的方案或增加席位。" oauth2RequestNotFound = "དབང་སྤྲོད་རེ་ཞུ་རྙེད་མ་བྱུང་།" oauth2InvalidUserInfoResponse = "སྤྱོད་མཁན་གྱི་གནས་ཚུལ་ལན་འདེབས་ནོར་འཁྲུལ།" oauth2invalidRequest = "རེ་ཞུ་ནོར་འཁྲུལ།" @@ -3846,14 +3950,17 @@ fitToWidth = "适应宽度" actualSize = "实际大小" [viewer] +cannotPreviewFile = "无法预览文件" +dualPageView = "双页视图" firstPage = "第一页" lastPage = "最后一页" -previousPage = "上一页" nextPage = "下一页" +onlyPdfSupported = "此查看器仅支持 PDF 文件。该文件似乎为其他格式。" +previousPage = "上一页" +singlePageView = "单页视图" +unknownFile = "未知文件" zoomIn = "放大" zoomOut = "缩小" -singlePageView = "单页视图" -dualPageView = "双页视图" [rightRail] closeSelected = "关闭所选文件" @@ -3877,6 +3984,7 @@ toggleSidebar = "切换侧边栏" exportSelected = "导出所选页面" toggleAnnotations = "切换注释可见性" annotationMode = "切换注释模式" +print = "打印 PDF" draw = "绘制" save = "保存" saveChanges = "保存更改" @@ -4494,6 +4602,7 @@ description = "Impressum 的 URL 或文件名(某些司法管辖区要求)" title = "高级与企业版" description = "配置你的高级或企业许可证密钥。" license = "许可证配置" +noInput = "请提供许可证密钥或文件" [admin.settings.premium.licenseKey] toggle = "有许可证密钥或证书文件?" @@ -4511,6 +4620,25 @@ line1 = "覆盖当前许可证密钥后将无法撤销。" line2 = "除非另有备份,否则之前的许可证将被永久丢失。" line3 = "重要:请妥善保管许可证密钥,切勿公开分享。" +[admin.settings.premium.inputMethod] +text = "许可证密钥" +file = "证书文件" + +[admin.settings.premium.file] +label = "许可证证书文件" +description = "上传您线下购买的 .lic 或 .cert 许可证文件" +choose = "选择许可证文件" +selected = "已选择:{{filename}}({{size}})" +successMessage = "许可证文件上传并激活成功。无需重启。" + +[admin.settings.premium.currentLicense] +title = "已激活的许可证" +file = "来源:许可证文件({{path}})" +key = "来源:许可证密钥" +type = "类型:{{type}}" +noInput = "请提供许可证密钥或上传证书文件" +success = "成功" + [admin.settings.premium.enabled] label = "启用高级功能" description = "为专业/企业功能启用许可证密钥检查" @@ -4644,7 +4772,9 @@ selectedCount = "已选 {{count}} 个" download = "下载" delete = "删除" unsupported = "不支持" +active = "已启用" addToUpload = "添加到上传" +closeFile = "关闭文件" deleteAll = "全部删除" loadingFiles = "正在加载文件..." noFiles = "暂无文件" @@ -5132,7 +5262,7 @@ upgrade = "立即升级 →" freeTitle = "服务器许可证" overLimitTitle = "需要服务器许可证" overLimitBody = "我们的许可每台服务器最多允许 {{freeTierLimit}} 名用户免费使用。您共有 {{overLimitUserCopy}} 名 Stirling 用户。为避免中断,请升级到 Stirling Server 方案 - 无限席位、PDF 文本编辑,以及每台服务器 $99/月 的完整管理员控制。" -freeBody = "我们的 开源内核(Open-Core) 许可允许每台服务器最多 {{freeTierLimit}} 名用户免费使用。为顺畅扩展并抢先体验全新的 PDF 文本编辑工具,我们推荐 Stirling Server 方案 - 完整编辑与 无限席位,$99/服务器/月。" +freeBody = "我们的Open-Core许可允许每台服务器最多{{freeTierLimit}}名用户免费使用。为实现不中断的扩展,我们推荐 Stirling Server 方案 - 不限席位并提供SSO 支持,$99/服务器/月。" [onboarding.desktopInstall] title = "下载" @@ -5237,6 +5367,31 @@ error = "用户状态更新失败" success = "用户删除成功" error = "删除用户失败" +[workspace.people.changePassword] +action = "更改密码" +title = "更改密码" +subtitle = "为其更新密码" +newPassword = "新密码" +confirmPassword = "确认密码" +placeholder = "输入新密码" +confirmPlaceholder = "再次输入新密码" +passwordRequired = "请输入新密码" +passwordMismatch = "两次输入的密码不一致" +generateRandom = "生成安全密码" +generatedPreview = "已生成的密码:" +copyTooltip = "复制到剪贴板" +copiedToClipboard = "密码已复制到剪贴板" +copyFailed = "复制密码失败" +sendEmail = "通过电子邮件通知用户此更改" +includePassword = "在邮件中包含新密码" +forcePasswordChange = "强制用户下次登录时更改密码" +emailUnavailable = "该用户的邮箱地址无效,已禁用通知。" +smtpDisabled = "邮件通知需要在设置中启用 SMTP。" +notifyOnly = "将发送不包含密码的邮件,告知用户管理员已更改其密码。" +submit = "更新密码" +success = "密码更新成功" +error = "密码更新失败" + [workspace.people.emailInvite] tab = "邮件邀请" description = "在下方输入或粘贴邮箱,使用逗号分隔。用户将通过邮件收到登录凭据。" @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "至少需要一个邮箱地址" submit = "发送邀请" success = "已成功邀请用户" -partialSuccess = "部分邀请失败" +partialFailure = "部分邀请发送失败" allFailed = "邀请用户失败" error = "发送邀请失败" @@ -5770,6 +5925,7 @@ subtitle = "使用您的 Stirling 账户登录" [setup.selfhosted] title = "登录服务器" subtitle = "输入您的服务器凭据" +link = "或连接到自托管账户" [setup.server] title = "连接到服务器" @@ -5788,6 +5944,14 @@ description = "输入自托管 Stirling PDF 服务器的完整 URL" emptyUrl = "请输入服务器 URL" unreachable = "无法连接到服务器" testFailed = "连接测试失败" +configFetch = "获取服务器配置失败。请检查 URL 后重试。" + +[setup.server.error.securityDisabled] +title = "未启用登录" +body = "此服务器未启用登录。要连接到该服务器,必须启用身份验证:" +step1 = "在环境中设置 DOCKER_ENABLE_SECURITY=true" +step2 = "或在 settings.yml 中设置 security.enableLogin=true" +step3 = "重启服务器" [setup.login] title = "登录" @@ -5797,6 +5961,13 @@ submit = "登录" signInWith = "登录方式" oauthPending = "正在打开浏览器进行认证..." orContinueWith = "或使用邮箱继续" +serverRequirement = "注意:服务器必须启用登录功能。" +showInstructions = "如何启用?" +hideInstructions = "隐藏说明" +instructions = "在您的 Stirling PDF 服务器上启用登录功能:" +instructionsEnvVar = "设置环境变量:" +instructionsOrYml = "或在 settings.yml 中:" +instructionsRestart = "然后重启服务器以使更改生效。" [setup.login.username] label = "用户名" @@ -5853,6 +6024,7 @@ earlyAccess = "抢先体验" reset = "重置更改" downloadJson = "下载 JSON" generatePdf = "生成 PDF" +saveChanges = "保存更改" [pdfTextEditor.options.autoScaleText] title = "自动缩放文本以适配框" @@ -5890,6 +6062,8 @@ alpha = "此 Alpha 预览器仍在演进中——某些字体、颜色、透明 [pdfTextEditor.empty] title = "未加载文档" subtitle = "加载 PDF 或 JSON 文件以开始编辑文本内容。" +dropzone = "将 PDF 或 JSON 文件拖放到此处,或点击浏览" +dropzoneWithFiles = "从“文件”选项卡选择文件,或将 PDF 或 JSON 文件拖放到此处,或点击浏览" [pdfTextEditor.welcomeBanner] title = "欢迎使用 PDF 文本编辑器(抢先体验)" diff --git a/frontend/public/locales/zh-CN/translation.toml b/frontend/public/locales/zh-CN/translation.toml index 2d166272c..ba345187f 100644 --- a/frontend/public/locales/zh-CN/translation.toml +++ b/frontend/public/locales/zh-CN/translation.toml @@ -163,6 +163,11 @@ unfavorite = "从收藏中移除" fullscreen = "切换到全屏模式" sidebar = "切换到侧边栏模式" +[backendStartup] +notFoundTitle = "未找到后端" +retry = "重试" +unreachable = "应用程序当前无法连接到后端。请检查后端状态和网络连接,然后重试。" + [zipWarning] title = "大型 ZIP 文件" message = "此 ZIP 包含 {{count}} 个文件。仍要解压?" @@ -912,6 +917,9 @@ desc = "通过串联 PDF 操作构建多步工作流。适合重复性任务。" desc = "将一个 PDF 叠加在另一个之上" title = "叠加 PDF" +[home.pdfTextEditor] +title = "PDF 文本编辑器" +desc = "编辑 PDF 中的现有文本和图像" [home.addText] tags = "文本,注释,标签" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "手写签名" defaultImageLabel = "已上传签名" defaultTextLabel = "键入签名" saveButton = "保存签名" +savePersonal = "保存到个人" +saveShared = "保存到共享" saveUnavailable = "请先创建签名再保存。" noChanges = "当前签名已保存。" +tempStorageTitle = "临时浏览器存储" +tempStorageDescription = "签名仅存储在您的浏览器中。清除浏览器数据或更换浏览器后将会丢失。" +personalHeading = "个人签名" +sharedHeading = "共享签名" +personalDescription = "只有您可以看到这些签名。" +sharedDescription = "所有用户都可以查看并使用这些签名。" [sign.saved.type] canvas = "手写" @@ -2574,7 +2590,7 @@ chooseP12File = "选择 PKCS12 文件" choosePfxFile = "选择 PFX 文件" choosePrivateKey = "选择私钥文件" location = "位置" -logoTitle = "Logo" +logoTitle = "徽标" name = "名称" noLogo = "无 Logo" pageNumber = "页码" @@ -3020,6 +3036,91 @@ title = "获取 PDF 信息" header = "获取 PDF 信息" submit = "获取信息" downloadJson = "下载 JSON" +processing = "正在提取信息..." +results = "结果" +noResults = "运行该工具以生成报告。" +downloads = "下载" +noneDetected = "未检测到" +indexTitle = "索引" + +[getPdfInfo.report] +entryLabel = "完整信息摘要" +shortTitle = "PDF 信息" + +[getPdfInfo.sections] +metadata = "元数据" +formFields = "表单字段" +basicInfo = "基本信息" +documentInfo = "文档信息" +compliance = "合规性" +encryption = "加密" +permissions = "权限" +other = "其他" +perPageInfo = "每页信息" +tableOfContents = "目录" + +[getPdfInfo.other] +attachments = "附件" +embeddedFiles = "嵌入文件" +javaScript = "JavaScript" +layers = "图层" +structureTree = "结构树" +xmp = "XMP 元数据" + +[getPdfInfo.perPage] +size = "尺寸" +annotations = "注释" +images = "图像" +links = "链接" +fonts = "字体" +xobjects = "XObject 数量" +multimedia = "多媒体" + +[getPdfInfo.summary] +pages = "页数" +fileSize = "文件大小" +pdfVersion = "PDF 版本" +language = "语言" +title = "PDF 摘要" +author = "作者" +created = "创建时间" +modified = "修改时间" +permsAll = "已允许所有权限" +permsRestricted = "{{count}} 项限制" +permsMixed = "部分权限受限" +hasCompliance = "符合合规标准" +noCompliance = "无合规标准" +basic = "基本信息" +documentInfo = "文档信息" +securityTitle = "安全状态" +technical = "技术" +overviewTitle = "PDF 概览" + +[getPdfInfo.summary.security] +encrypted = "已加密 PDF - 存在密码保护" +unencrypted = "未加密 PDF - 无密码保护" + +[getPdfInfo.summary.tech] +images = "图像" +fonts = "字体" +formFields = "表单字段" +embeddedFiles = "嵌入文件" +javaScript = "JavaScript" +layers = "图层" +bookmarks = "书签" +multimedia = "多媒体" + +[getPdfInfo.summary.overview] +untitled = "未命名文档" +unknown = "未知作者" +text = "这是一个 {{pages}} 页的 PDF,标题为 {{title}},作者为 {{author}}(PDF 版本 {{version}})。" + +[getPdfInfo.error] +partial = "部分文件无法处理。" +unexpected = "提取过程中发生意外错误。" + +[getPdfInfo.status] +complete = "提取完成" [extractPage] tags = "提取" @@ -3438,6 +3539,9 @@ signinTitle = "请登录" ssoSignIn = "通过单点登录登录" oAuth2AutoCreateDisabled = "OAuth2 自动创建用户已禁用" oAuth2AdminBlockedUser = "目前已阻止未注册用户的注册或登录。请联系管理员。" +oAuth2RequiresLicense = "OAuth/SSO 登录需要付费许可(Server 或 Enterprise)。请联系管理员以升级您的计划。" +saml2RequiresLicense = "SAML 登录需要付费许可(Server 或 Enterprise)。请联系管理员以升级您的计划。" +maxUsersReached = "您当前的许可已达到用户数量上限。请联系管理员以升级您的计划或增加席位。" oauth2RequestNotFound = "找不到验证请求" oauth2InvalidUserInfoResponse = "无效的用户信息响应" oauth2invalidRequest = "无效请求" @@ -3846,14 +3950,17 @@ fitToWidth = "适配宽度" actualSize = "实际大小" [viewer] +cannotPreviewFile = "无法预览文件" +dualPageView = "双页视图" firstPage = "第一页" lastPage = "最后一页" -previousPage = "上一页" nextPage = "下一页" +onlyPdfSupported = "该查看器仅支持 PDF 文件。此文件似乎是其他格式。" +previousPage = "上一页" +singlePageView = "单页视图" +unknownFile = "未知文件" zoomIn = "放大" zoomOut = "缩小" -singlePageView = "单页视图" -dualPageView = "双页视图" [rightRail] closeSelected = "关闭所选文件" @@ -3877,6 +3984,7 @@ toggleSidebar = "切换侧边栏" exportSelected = "导出所选页面" toggleAnnotations = "切换注释可见性" annotationMode = "切换注释模式" +print = "打印 PDF" draw = "绘制" save = "保存" saveChanges = "保存更改" @@ -4494,6 +4602,7 @@ description = "Impressum 的 URL 或文件名(某些司法辖区要求)" title = "高级版与企业版" description = "配置您的高级版或企业版许可证密钥。" license = "许可证配置" +noInput = "请提供许可证密钥或文件" [admin.settings.premium.licenseKey] toggle = "有许可证密钥或证书文件吗?" @@ -4511,6 +4620,25 @@ line1 = "覆盖当前许可证密钥后将无法撤销。" line2 = "除非已在其他位置备份,否则您之前的许可证将被永久丢失。" line3 = "重要:请妥善保管许可证密钥,切勿公开分享。" +[admin.settings.premium.inputMethod] +text = "许可证密钥" +file = "证书文件" + +[admin.settings.premium.file] +label = "许可证证书文件" +description = "上传您线下购买的 .lic 或 .cert 许可证文件" +choose = "选择许可证文件" +selected = "已选择:{{filename}}({{size}})" +successMessage = "许可证文件已成功上传并激活。无需重启。" + +[admin.settings.premium.currentLicense] +title = "已激活的许可证" +file = "来源:许可证文件({{path}})" +key = "来源:许可证密钥" +type = "类型:{{type}}" +noInput = "请提供许可证密钥或上传证书文件" +success = "成功" + [admin.settings.premium.enabled] label = "启用高级功能" description = "启用对专业/企业功能的许可证密钥检查" @@ -4622,7 +4750,7 @@ searchFiles = "搜索文件…" recent = "最近" localFiles = "本地文件" googleDrive = "Google 云端硬盘" -googleDriveShort = "Drive" +googleDriveShort = "云端硬盘" myFiles = "我的文件" noRecentFiles = "未找到最近文件" googleDriveNotAvailable = "不可使用 Google 云端硬盘集成" @@ -4644,7 +4772,9 @@ selectedCount = "已选 {{count}}" download = "下载" delete = "删除" unsupported = "不支持" +active = "活跃" addToUpload = "添加至上传" +closeFile = "关闭文件" deleteAll = "删除全部" loadingFiles = "正在加载文件..." noFiles = "没有可用的文件" @@ -5132,7 +5262,7 @@ upgrade = "立即升级 →" freeTitle = "服务器许可证" overLimitTitle = "需要服务器许可证" overLimitBody = "我们的许可允许每台服务器最多免费 {{freeTierLimit}} 名用户。您有 {{overLimitUserCopy}} 名 Stirling 用户。为不间断使用,请升级至 Stirling Server 方案 - 无限席位、PDF 文本编辑,以及 $99/server/mo 的完整管理员控制。" -freeBody = "我们的 Open-Core 许可允许每台服务器最多免费 {{freeTierLimit}} 名用户。为无缝扩展并抢先体验全新的 PDF 文本编辑工具,推荐 Stirling Server 方案 - 完整编辑与 无限席位,$99/server/mo。" +freeBody = "我们的 Open-Core 许可允许每台服务器最多 {{freeTierLimit}} 名用户免费使用。为实现不间断扩展,我们推荐 Stirling Server 方案 - 无限席位SSO 支持,$99/server/mo." [onboarding.desktopInstall] title = "下载" @@ -5237,6 +5367,31 @@ error = "更新用户状态失败" success = "用户删除成功" error = "删除用户失败" +[workspace.people.changePassword] +action = "更改密码" +title = "更改密码" +subtitle = "为以下用户更新密码" +newPassword = "新密码" +confirmPassword = "确认密码" +placeholder = "输入新密码" +confirmPlaceholder = "再次输入新密码" +passwordRequired = "请输入新密码" +passwordMismatch = "两次输入的密码不一致" +generateRandom = "生成安全密码" +generatedPreview = "生成的密码:" +copyTooltip = "复制到剪贴板" +copiedToClipboard = "密码已复制到剪贴板" +copyFailed = "复制密码失败" +sendEmail = "向用户发送有关此更改的邮件" +includePassword = "在邮件中包含新密码" +forcePasswordChange = "强制用户下次登录时更改密码" +emailUnavailable = "该用户的邮箱地址无效。通知已禁用。" +smtpDisabled = "邮件通知需要在设置中启用 SMTP。" +notifyOnly = "将发送不含密码的邮件,告知用户管理员已更改了密码。" +submit = "更新密码" +success = "密码更新成功" +error = "更新密码失败" + [workspace.people.emailInvite] tab = "电子邮件邀请" description = "在下方输入或粘贴电子邮件地址,用逗号分隔。用户将通过电子邮件收到登录凭据。" @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "至少需要一个电子邮件地址" submit = "发送邀请" success = "已成功邀请用户" -partialSuccess = "部分邀请失败" +partialFailure = "部分邀请失败" allFailed = "邀请用户失败" error = "发送邀请失败" @@ -5770,6 +5925,7 @@ subtitle = "使用您的 Stirling 账户登录" [setup.selfhosted] title = "登录到服务器" subtitle = "输入您的服务器凭据" +link = "或连接到自托管账户" [setup.server] title = "连接到服务器" @@ -5788,6 +5944,14 @@ description = "输入您自托管 Stirling PDF 服务器的完整 URL" emptyUrl = "请输入服务器 URL" unreachable = "无法连接到服务器" testFailed = "连接测试失败" +configFetch = "获取服务器配置失败。请检查 URL 并重试。" + +[setup.server.error.securityDisabled] +title = "未启用登录" +body = "此服务器未启用登录。要连接到此服务器,必须启用身份验证:" +step1 = "在环境中设置 DOCKER_ENABLE_SECURITY=true" +step2 = "或在 settings.yml 中设置 security.enableLogin=true" +step3 = "重启服务器" [setup.login] title = "登录" @@ -5797,6 +5961,13 @@ submit = "登录" signInWith = "使用以下方式登录" oauthPending = "正在打开浏览器进行认证..." orContinueWith = "或使用 Email 继续" +serverRequirement = "注意:服务器必须启用登录功能。" +showInstructions = "如何启用?" +hideInstructions = "隐藏说明" +instructions = "要在您的 Stirling PDF 服务器上启用登录:" +instructionsEnvVar = "设置环境变量:" +instructionsOrYml = "或在 settings.yml 中:" +instructionsRestart = "然后重启服务器以使更改生效。" [setup.login.username] label = "用户名" @@ -5853,6 +6024,7 @@ earlyAccess = "抢先体验" reset = "重置更改" downloadJson = "下载 JSON" generatePdf = "生成 PDF" +saveChanges = "保存更改" [pdfTextEditor.options.autoScaleText] title = "自动缩放文本以适配框体" @@ -5890,6 +6062,8 @@ alpha = "此 Alpha 预览器仍在演进中——某些字体、颜色、透明 [pdfTextEditor.empty] title = "未加载文档" subtitle = "加载 PDF 或 JSON 文件以开始编辑文本内容。" +dropzone = "将 PDF 或 JSON 文件拖放到此处,或点击浏览" +dropzoneWithFiles = "从“文件”选项卡选择文件,或将 PDF 或 JSON 文件拖放到此处,或点击浏览" [pdfTextEditor.welcomeBanner] title = "欢迎使用 PDF 文本编辑器(抢先体验)" diff --git a/frontend/public/locales/zh-TW/translation.toml b/frontend/public/locales/zh-TW/translation.toml index 55b4e6b52..a15298988 100644 --- a/frontend/public/locales/zh-TW/translation.toml +++ b/frontend/public/locales/zh-TW/translation.toml @@ -163,6 +163,11 @@ unfavorite = "從我的最愛移除" fullscreen = "切換至全螢幕模式" sidebar = "切換至側邊欄模式" +[backendStartup] +notFoundTitle = "找不到後端" +retry = "重試" +unreachable = "應用程式目前無法連線至後端。請確認後端狀態與網路連線,然後再試一次。" + [zipWarning] title = "大型 ZIP 檔案" message = "此 ZIP 包含 {{count}} 個檔案。仍要解壓縮嗎?" @@ -912,6 +917,9 @@ desc = "將多個 PDF 動作串接,建立多步驟工作流程。適合重複 desc = "將 PDF 覆蓋在另一個 PDF 上" title = "覆蓋 PDF" +[home.pdfTextEditor] +title = "PDF 文字編輯器" +desc = "編輯 PDF 中既有的文字與圖片" [home.addText] tags = "文字,註解,標籤" @@ -1173,7 +1181,7 @@ selectFilesPlaceholder = "在主視圖選取檔案以開始" settings = "設定" conversionCompleted = "轉換完成" results = "結果" -defaultFilename = "converted_file" +defaultFilename = "已轉換_檔案" conversionResults = "轉換結果" convertFrom = "來源格式" convertTo = "目標格式" @@ -1360,7 +1368,7 @@ title = "新增浮水印" desc = "將文字或影像浮水印加入 PDF 檔案" completed = "已加入浮水印" submit = "新增浮水印" -filenamePrefix = "watermarked" +filenamePrefix = "已加_浮水印" [watermark.error] failed = "為 PDF 新增浮水印時發生錯誤。" @@ -2259,8 +2267,16 @@ defaultCanvasLabel = "手繪簽名" defaultImageLabel = "上傳的簽名" defaultTextLabel = "輸入的簽名" saveButton = "儲存簽名" +savePersonal = "儲存為個人" +saveShared = "儲存為共用" saveUnavailable = "請先建立簽名才能儲存。" noChanges = "目前簽名已儲存。" +tempStorageTitle = "瀏覽器暫存" +tempStorageDescription = "簽名僅儲存在您的瀏覽器中。若清除瀏覽資料或更換瀏覽器,將會遺失。" +personalHeading = "個人簽名" +sharedHeading = "共用簽名" +personalDescription = "只有您能看到這些簽名。" +sharedDescription = "所有使用者都可以查看並使用這些簽名。" [sign.saved.type] canvas = "手繪" @@ -3020,6 +3036,91 @@ title = "取得 PDF 資訊" header = "取得 PDF 資訊" submit = "取得資訊" downloadJson = "下載 JSON" +processing = "正在擷取資訊..." +results = "結果" +noResults = "執行此工具以產生報告。" +downloads = "下載" +noneDetected = "未偵測到" +indexTitle = "索引" + +[getPdfInfo.report] +entryLabel = "完整資訊摘要" +shortTitle = "PDF 資訊" + +[getPdfInfo.sections] +metadata = "中繼資料" +formFields = "表單欄位" +basicInfo = "基本資訊" +documentInfo = "文件資訊" +compliance = "符合性" +encryption = "加密" +permissions = "權限" +other = "其他" +perPageInfo = "每頁資訊" +tableOfContents = "目錄" + +[getPdfInfo.other] +attachments = "附件" +embeddedFiles = "內嵌檔案" +javaScript = "JavaScript" +layers = "圖層" +structureTree = "結構樹" +xmp = "XMPMetadata" + +[getPdfInfo.perPage] +size = "尺寸" +annotations = "註解" +images = "影像" +links = "連結" +fonts = "字型" +xobjects = "XObject 數量" +multimedia = "多媒體" + +[getPdfInfo.summary] +pages = "頁數" +fileSize = "檔案大小" +pdfVersion = "PDF 版本" +language = "語言" +title = "PDF 摘要" +author = "作者" +created = "建立時間" +modified = "修改時間" +permsAll = "允許所有權限" +permsRestricted = "{{count}} 個限制" +permsMixed = "部分權限受限" +hasCompliance = "具有符合性標準" +noCompliance = "無符合性標準" +basic = "基本資訊" +documentInfo = "文件資訊" +securityTitle = "安全性狀態" +technical = "技術資訊" +overviewTitle = "PDF 概覽" + +[getPdfInfo.summary.security] +encrypted = "已加密的 PDF - 具有密碼保護" +unencrypted = "未加密的 PDF - 無密碼保護" + +[getPdfInfo.summary.tech] +images = "影像" +fonts = "字型" +formFields = "表單欄位" +embeddedFiles = "內嵌檔案" +javaScript = "JavaScript" +layers = "圖層" +bookmarks = "書籤" +multimedia = "多媒體" + +[getPdfInfo.summary.overview] +untitled = "未命名文件" +unknown = "作者不詳" +text = "這是一份 {{pages}} 頁的 PDF,標題為 {{title}},作者 {{author}}(PDF 版本 {{version}})。" + +[getPdfInfo.error] +partial = "部分檔案無法處理。" +unexpected = "擷取過程發生非預期錯誤。" + +[getPdfInfo.status] +complete = "擷取完成" [extractPage] tags = "提取" @@ -3438,6 +3539,9 @@ signinTitle = "請登入" ssoSignIn = "透過 SSO 單一登入" oAuth2AutoCreateDisabled = "OAuth 2.0 自動建立使用者功能已停用" oAuth2AdminBlockedUser = "目前不允許未註冊的使用者註冊或登入。請聯絡系統管理員。" +oAuth2RequiresLicense = "OAuth/SSO 登入需要付費授權(Server 或 Enterprise)。請聯絡管理員升級您的方案。" +saml2RequiresLicense = "SAML 登入需要付費授權(Server 或 Enterprise)。請聯絡管理員升級您的方案。" +maxUsersReached = "您目前的授權已達使用者上限。請聯絡管理員以升級方案或新增席次。" oauth2RequestNotFound = "找不到驗證請求" oauth2InvalidUserInfoResponse = "使用者資訊回應無效" oauth2invalidRequest = "請求無效" @@ -3533,7 +3637,7 @@ title = "PDF 轉為單一頁面" header = "PDF 轉為單一頁面" submit = "轉換為單一頁面" description = "此工具會將 PDF 的所有頁面合併成一個大型單頁。寬度將與原頁相同,但高度會是所有頁面高度之總和。" -filenamePrefix = "single_page" +filenamePrefix = "單一_頁面" [pdfToSinglePage.files] placeholder = "在主畫面選擇一個 PDF 檔開始使用" @@ -3846,14 +3950,17 @@ fitToWidth = "適合寬度" actualSize = "實際大小" [viewer] +cannotPreviewFile = "無法預覽檔案" +dualPageView = "雙頁檢視" firstPage = "第一頁" lastPage = "最後一頁" -previousPage = "上一頁" nextPage = "下一頁" +onlyPdfSupported = "此檢視器僅支援 PDF 檔案。此檔案似乎是其他格式。" +previousPage = "上一頁" +singlePageView = "單頁檢視" +unknownFile = "未知檔案" zoomIn = "放大" zoomOut = "縮小" -singlePageView = "單頁檢視" -dualPageView = "雙頁檢視" [rightRail] closeSelected = "關閉已選檔案" @@ -3877,6 +3984,7 @@ toggleSidebar = "切換側邊欄" exportSelected = "匯出選取的頁面" toggleAnnotations = "切換註解可見度" annotationMode = "切換註解模式" +print = "列印 PDF" draw = "繪圖" save = "儲存" saveChanges = "儲存變更" @@ -4494,6 +4602,7 @@ description = "Impressum 的 URL 或檔名(某些司法管轄區要求提供 title = "Premium 與 Enterprise" description = "設定您的 Premium 或 Enterprise 授權金鑰。" license = "擴充授權設定" +noInput = "請提供授權金鑰或檔案" [admin.settings.premium.licenseKey] toggle = "有授權金鑰或憑證檔嗎?" @@ -4511,6 +4620,25 @@ line1 = "覆寫目前的授權金鑰後將無法復原。" line2 = "除非你另有備份,否則先前的授權將永久遺失。" line3 = "重要:請妥善保管授權金鑰並保持私密,切勿公開分享。" +[admin.settings.premium.inputMethod] +text = "授權金鑰" +file = "憑證檔案" + +[admin.settings.premium.file] +label = "授權憑證檔案" +description = "上傳您離線購買的 .lic 或 .cert 授權檔案" +choose = "選擇授權檔案" +selected = "已選取:{{filename}}({{size}})" +successMessage = "授權檔案已成功上傳並啟用,無需重新啟動。" + +[admin.settings.premium.currentLicense] +title = "使用中的授權" +file = "來源:授權檔案({{path}})" +key = "來源:授權金鑰" +type = "類型:{{type}}" +noInput = "請提供授權金鑰或上傳憑證檔案" +success = "成功" + [admin.settings.premium.enabled] label = "啟用 Premium 功能" description = "啟用對進階/企業功能的授權金鑰檢查" @@ -4644,7 +4772,9 @@ selectedCount = "{{count}} 個已選" download = "下載" delete = "刪除" unsupported = "不支援" +active = "啟用" addToUpload = "加入上傳" +closeFile = "關閉檔案" deleteAll = "全部刪除" loadingFiles = "正在載入檔案..." noFiles = "沒有可用的檔案" @@ -5132,7 +5262,7 @@ upgrade = "立即升級 →" freeTitle = "伺服器授權" overLimitTitle = "需要伺服器授權" overLimitBody = "我們的授權允許每台伺服器最多 {{freeTierLimit}} 位使用者免費使用。你有 {{overLimitUserCopy}} 位 Stirling 使用者。若要不中斷使用,請升級至 Stirling Server 方案 - 不限席次、PDF 文字編輯,以及完整管理控制,每台伺服器 $99/月。" -freeBody = "我們的 Open-Core 授權允許每台伺服器最多 {{freeTierLimit}} 位使用者免費使用。若要無縫擴充並搶先體驗全新的 PDF 文字編輯工具,建議升級至 Stirling Server 方案 - 完整編輯與 不限席次,每台伺服器 $99/月。" +freeBody = "我們的 Open-Core 授權允許每台伺服器最多 {{freeTierLimit}} 位使用者免費使用。若要無縫擴充,我們建議選用 Stirling Server 方案 - 不限席次SSO 支援,每伺服器每月 $99。" [onboarding.desktopInstall] title = "下載" @@ -5237,6 +5367,31 @@ error = "更新使用者狀態失敗" success = "已成功刪除使用者" error = "刪除使用者失敗" +[workspace.people.changePassword] +action = "變更密碼" +title = "變更密碼" +subtitle = "為使用者更新密碼" +newPassword = "新密碼" +confirmPassword = "確認密碼" +placeholder = "輸入新密碼" +confirmPlaceholder = "再次輸入新密碼" +passwordRequired = "請輸入新密碼" +passwordMismatch = "密碼不相符" +generateRandom = "產生安全密碼" +generatedPreview = "產生的密碼:" +copyTooltip = "複製到剪貼簿" +copiedToClipboard = "已將密碼複製到剪貼簿" +copyFailed = "無法複製密碼" +sendEmail = "以 Email 通知使用者此變更" +includePassword = "在 Email 中包含新密碼" +forcePasswordChange = "強制使用者下次登入時變更密碼" +emailUnavailable = "此使用者的 Email 不是有效的 Email 位址。已停用通知。" +smtpDisabled = "Email 通知需要在設定中啟用 SMTP。" +notifyOnly = "將傳送不含密碼的 Email,通知使用者管理員已變更其密碼。" +submit = "更新密碼" +success = "密碼已成功更新" +error = "更新密碼失敗" + [workspace.people.emailInvite] tab = "電子郵件邀請" description = "在下方輸入或貼上電子郵件,使用逗號分隔。使用者將透過電子郵件收到登入憑證。" @@ -5245,7 +5400,7 @@ emailsPlaceholder = "user1@example.com, user2@example.com" emailsRequired = "至少需要一個電子郵件地址" submit = "發送邀請" success = "已成功邀請使用者" -partialSuccess = "部分邀請失敗" +partialFailure = "部分邀請失敗" allFailed = "邀請使用者失敗" error = "發送邀請失敗" @@ -5770,6 +5925,7 @@ subtitle = "使用你的 Stirling 帳戶登入" [setup.selfhosted] title = "登入伺服器" subtitle = "輸入你的伺服器認證資訊" +link = "或連線到自行託管的帳戶" [setup.server] title = "連線至伺服器" @@ -5788,6 +5944,14 @@ description = "輸入你自託管 Stirling PDF 伺服器的完整 URL" emptyUrl = "請輸入伺服器 URL" unreachable = "無法連線至伺服器" testFailed = "連線測試失敗" +configFetch = "無法擷取伺服器組態。請檢查 URL 後再試一次。" + +[setup.server.error.securityDisabled] +title = "未啟用登入" +body = "此伺服器未啟用登入。若要連線至此伺服器,您必須啟用驗證:" +step1 = "在您的環境中設定 DOCKER_ENABLE_SECURITY=true" +step2 = "或在 settings.yml 中將 security.enableLogin 設為 true" +step3 = "重新啟動伺服器" [setup.login] title = "登入" @@ -5797,13 +5961,20 @@ submit = "登入" signInWith = "以此登入" oauthPending = "正在開啟瀏覽器進行驗證..." orContinueWith = "或改用 Email 繼續" +serverRequirement = "注意:伺服器必須啟用登入功能。" +showInstructions = "如何啟用?" +hideInstructions = "隱藏說明" +instructions = "在您的 Stirling PDF 伺服器上啟用登入功能:" +instructionsEnvVar = "設定環境變數:" +instructionsOrYml = "或在 settings.yml 中:" +instructionsRestart = "然後重新啟動伺服器以套用變更。" [setup.login.username] label = "使用者名稱" placeholder = "輸入你的使用者名稱" [setup.login.email] -label = "Email" +label = "電子郵件" placeholder = "輸入你的 Email" [setup.login.password] @@ -5853,6 +6024,7 @@ earlyAccess = "搶先體驗" reset = "重設變更" downloadJson = "下載 JSON" generatePdf = "產生 PDF" +saveChanges = "儲存變更" [pdfTextEditor.options.autoScaleText] title = "自動縮放文字以符合方框" @@ -5890,6 +6062,8 @@ alpha = "此 Alpha 檢視器仍在演進中——部分字型、顏色、透明 [pdfTextEditor.empty] title = "尚未載入文件" subtitle = "載入 PDF 或 JSON 檔以開始編輯文字內容。" +dropzone = "將 PDF 或 JSON 檔案拖放到此處,或點擊以瀏覽" +dropzoneWithFiles = "從「檔案」分頁選取檔案,或將 PDF 或 JSON 檔案拖放到此處,或點擊以瀏覽" [pdfTextEditor.welcomeBanner] title = "歡迎使用 PDF 文字編輯器(搶先體驗)" diff --git a/frontend/src-tauri/Cargo.lock b/frontend/src-tauri/Cargo.lock index 9d2395e2d..2689b6b98 100644 --- a/frontend/src-tauri/Cargo.lock +++ b/frontend/src-tauri/Cargo.lock @@ -589,6 +589,26 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom 0.2.16", + "once_cell", + "tiny-keccak", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -716,6 +736,12 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + [[package]] name = "crypto-common" version = "0.1.6" @@ -908,6 +934,15 @@ dependencies = [ "syn 2.0.108", ] +[[package]] +name = "dlv-list" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "442039f5147480ba31067cb00ada1adae6892028e40e45fc5de7b7df6dcc1b5f" +dependencies = [ + "const-random", +] + [[package]] name = "document-features" version = "0.2.12" @@ -1626,6 +1661,12 @@ dependencies = [ "ahash", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.16.0" @@ -2152,7 +2193,11 @@ version = "3.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eebcc3aff044e5944a8fbaf69eb277d11986064cba30c468730e8b9909fb551c" dependencies = [ + "byteorder", "log", + "security-framework 2.11.1", + "security-framework 3.5.1", + "windows-sys 0.60.2", "zeroize", ] @@ -2378,7 +2423,7 @@ dependencies = [ "openssl-probe", "openssl-sys", "schannel", - "security-framework", + "security-framework 2.11.1", "security-framework-sys", "tempfile", ] @@ -2816,6 +2861,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-multimap" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49203cdcae0030493bad186b28da2fa25645fa276a51b6fec8010d281e02ef79" +dependencies = [ + "dlv-list", + "hashbrown 0.14.5", +] + [[package]] name = "ordered-stream" version = "0.2.0" @@ -3647,6 +3702,16 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "rust-ini" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "796e8d2b6696392a43bea58116b667fb4c29727dc5abd27d6acf338bb4f688c7" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + [[package]] name = "rust_decimal" version = "1.39.0" @@ -3841,6 +3906,19 @@ dependencies = [ "security-framework-sys", ] +[[package]] +name = "security-framework" +version = "3.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3297343eaf830f66ede390ea39da1d462b6b0c1b000f420d0a83f898bbbe6ef" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + [[package]] name = "security-framework-sys" version = "2.15.0" @@ -4239,6 +4317,7 @@ dependencies = [ "sha2", "tauri", "tauri-build", + "tauri-plugin-deep-link", "tauri-plugin-fs", "tauri-plugin-http", "tauri-plugin-log", @@ -4598,6 +4677,27 @@ dependencies = [ "walkdir", ] +[[package]] +name = "tauri-plugin-deep-link" +version = "2.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e82759f7c7d51de3cbde51c04b3f2332de52436ed84541182cd8944b04e9e73" +dependencies = [ + "dunce", + "plist", + "rust-ini", + "serde", + "serde_json", + "tauri", + "tauri-plugin", + "tauri-utils", + "thiserror 2.0.17", + "tracing", + "url", + "windows-registry", + "windows-result 0.3.4", +] + [[package]] name = "tauri-plugin-fs" version = "2.4.4" @@ -4718,6 +4818,7 @@ dependencies = [ "serde", "serde_json", "tauri", + "tauri-plugin-deep-link", "thiserror 2.0.17", "tracing", "windows-sys 0.60.2", @@ -4937,6 +5038,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tiny_http" version = "0.12.0" diff --git a/frontend/src-tauri/Cargo.toml b/frontend/src-tauri/Cargo.toml index 6884bd178..455436cdf 100644 --- a/frontend/src-tauri/Cargo.toml +++ b/frontend/src-tauri/Cargo.toml @@ -29,10 +29,11 @@ tauri-plugin-log = "2.0.0-rc" tauri-plugin-shell = "2.1.0" tauri-plugin-fs = "2.4.4" tauri-plugin-http = "2.4.4" -tauri-plugin-single-instance = "2.0.1" +tauri-plugin-single-instance = { version = "2.3.6", features = ["deep-link"] } tauri-plugin-store = "2.1.0" tauri-plugin-opener = "2.0.0" -keyring = "3.6.1" +tauri-plugin-deep-link = "2.4.5" +keyring = { version = "3.6.1", features = ["apple-native", "windows-native"] } tokio = { version = "1.0", features = ["time", "sync"] } reqwest = { version = "0.11", features = ["json"] } tiny_http = "0.12" diff --git a/frontend/src-tauri/capabilities/default.json b/frontend/src-tauri/capabilities/default.json index b992b3221..445e1d30a 100644 --- a/frontend/src-tauri/capabilities/default.json +++ b/frontend/src-tauri/capabilities/default.json @@ -19,6 +19,8 @@ { "identifier": "fs:allow-read-file", "allow": [{ "path": "**" }] - } + }, + "opener:default", + "shell:allow-open" ] } diff --git a/frontend/src-tauri/src/commands/auth.rs b/frontend/src-tauri/src/commands/auth.rs index 30ec0d6c4..3e75b452e 100644 --- a/frontend/src-tauri/src/commands/auth.rs +++ b/frontend/src-tauri/src/commands/auth.rs @@ -1,4 +1,4 @@ -use keyring::Entry; +use keyring::{Entry}; use serde::{Deserialize, Serialize}; use std::sync::{Arc, Mutex}; use tauri::AppHandle; @@ -21,53 +21,70 @@ pub struct UserInfo { } fn get_keyring_entry() -> Result { - Entry::new(KEYRING_SERVICE, KEYRING_TOKEN_KEY) - .map_err(|e| format!("Failed to access keyring: {}", e)) + log::debug!("Creating keyring entry with service='{}' username='{}'", KEYRING_SERVICE, KEYRING_TOKEN_KEY); + let entry = Entry::new(KEYRING_SERVICE, KEYRING_TOKEN_KEY) + .map_err(|e| { + log::error!("Failed to create keyring entry: {}", e); + format!("Failed to access keyring: {}", e) + })?; + log::debug!("Keyring entry created successfully"); + Ok(entry) } #[tauri::command] pub async fn save_auth_token(_app_handle: AppHandle, token: String) -> Result<(), String> { - log::info!("Saving auth token to keyring"); + if token.is_empty() { + log::warn!("Attempted to save empty auth token"); + return Err("Token cannot be empty".to_string()); + } let entry = get_keyring_entry()?; entry .set_password(&token) - .map_err(|e| format!("Failed to save token to keyring: {}", e))?; + .map_err(|e| { + log::error!("Failed to set password in keyring: {}", e); + format!("Failed to save token to keyring: {}", e) + })?; + + // Verify the save worked + match entry.get_password() { + Ok(retrieved_token) => { + if retrieved_token != token { + log::error!("Token verification failed: Retrieved token doesn't match"); + return Err("Token verification failed after save".to_string()); + } + } + Err(e) => { + log::error!("Token verification failed: {}", e); + return Err(format!("Token verification failed: {}", e)); + } + } - log::info!("Auth token saved successfully"); Ok(()) } #[tauri::command] pub async fn get_auth_token(_app_handle: AppHandle) -> Result, String> { - log::debug!("Retrieving auth token from keyring"); - let entry = get_keyring_entry()?; match entry.get_password() { Ok(token) => Ok(Some(token)), Err(keyring::Error::NoEntry) => Ok(None), - Err(e) => Err(format!("Failed to retrieve token: {}", e)), + Err(e) => { + log::error!("Failed to retrieve token from keyring: {}", e); + Err(format!("Failed to retrieve token: {}", e)) + }, } } #[tauri::command] pub async fn clear_auth_token(_app_handle: AppHandle) -> Result<(), String> { - log::info!("Clearing auth token from keyring"); - let entry = get_keyring_entry()?; // Delete the token - ignore error if it doesn't exist match entry.delete_credential() { - Ok(_) => { - log::info!("Auth token cleared successfully"); - Ok(()) - } - Err(keyring::Error::NoEntry) => { - log::info!("Auth token was already cleared"); - Ok(()) - } + Ok(_) | Err(keyring::Error::NoEntry) => Ok(()), Err(e) => Err(format!("Failed to clear token: {}", e)), } } @@ -78,8 +95,6 @@ pub async fn save_user_info( username: String, email: Option, ) -> Result<(), String> { - log::info!("Saving user info for: {}", username); - let user_info = UserInfo { username, email }; let store = app_handle @@ -96,7 +111,6 @@ pub async fn save_user_info( .save() .map_err(|e| format!("Failed to save store: {}", e))?; - log::info!("User info saved successfully"); Ok(()) } @@ -117,8 +131,6 @@ pub async fn get_user_info(app_handle: AppHandle) -> Result, St #[tauri::command] pub async fn clear_user_info(app_handle: AppHandle) -> Result<(), String> { - log::info!("Clearing user info"); - let store = app_handle .store(STORE_FILE) .map_err(|e| format!("Failed to access store: {}", e))?; @@ -129,7 +141,6 @@ pub async fn clear_user_info(app_handle: AppHandle) -> Result<(), String> { .save() .map_err(|e| format!("Failed to save store: {}", e))?; - log::info!("User info cleared successfully"); Ok(()) } @@ -186,12 +197,8 @@ pub async fn login( supabase_key: String, saas_server_url: String, ) -> Result { - log::info!("Login attempt for user: {} to server: {}", username, server_url); - // Detect if this is Supabase (SaaS) or Spring Boot (self-hosted) - // Compare against the configured SaaS server URL let is_supabase = server_url.trim_end_matches('/') == saas_server_url.trim_end_matches('/'); - log::info!("Authentication type: {}", if is_supabase { "Supabase (SaaS)" } else { "Spring Boot (Self-hosted)" }); // Create HTTP client let client = reqwest::Client::new(); @@ -248,8 +255,6 @@ pub async fn login( .or_else(|| email.clone()) .unwrap_or_else(|| username); - log::info!("Supabase login successful for user: {}", username); - Ok(LoginResponse { token: login_response.access_token, username, diff --git a/frontend/src-tauri/src/lib.rs b/frontend/src-tauri/src/lib.rs index cb8faf04b..61cbd6d43 100644 --- a/frontend/src-tauri/src/lib.rs +++ b/frontend/src-tauri/src/lib.rs @@ -1,4 +1,4 @@ -use tauri::{Manager, RunEvent, WindowEvent, Emitter}; +use tauri::{AppHandle, Emitter, Manager, RunEvent, WindowEvent}; mod utils; mod commands; @@ -28,6 +28,17 @@ use commands::{ }; use state::connection_state::AppConnectionState; use utils::{add_log, get_tauri_logs}; +use tauri_plugin_deep_link::DeepLinkExt; + +fn dispatch_deep_link(app: &AppHandle, url: &str) { + add_log(format!("🔗 Dispatching deep link: {}", url)); + let _ = app.emit("deep-link", url.to_string()); + + if let Some(window) = app.get_webview_window("main") { + let _ = window.set_focus(); + let _ = window.unminimize(); + } +} #[cfg_attr(mobile, tauri::mobile_entry_point)] pub fn run() { @@ -42,6 +53,7 @@ pub fn run() { .plugin(tauri_plugin_fs::init()) .plugin(tauri_plugin_http::init()) .plugin(tauri_plugin_store::Builder::new().build()) + .plugin(tauri_plugin_deep_link::init()) .manage(AppConnectionState::default()) .plugin(tauri_plugin_single_instance::init(|app, args, _cwd| { // This callback runs when a second instance tries to start @@ -78,6 +90,29 @@ pub fn run() { } } + { + let app_handle = app.handle(); + // On macOS the plugin registers schemes via bundle metadata, so runtime registration is required only on Windows/Linux + #[cfg(any(target_os = "linux", target_os = "windows"))] + if let Err(err) = app_handle.deep_link().register_all() { + add_log(format!("⚠️ Failed to register deep link handler: {}", err)); + } + + if let Ok(Some(urls)) = app_handle.deep_link().get_current() { + let initial_handle = app_handle.clone(); + for url in urls { + dispatch_deep_link(&initial_handle, url.as_str()); + } + } + + let event_app_handle = app_handle.clone(); + app_handle.deep_link().on_open_url(move |event| { + for url in event.urls() { + dispatch_deep_link(&event_app_handle, url.as_str()); + } + }); + } + // Start backend immediately, non-blocking let app_handle = app.handle().clone(); @@ -152,15 +187,27 @@ pub fn run() { } #[cfg(target_os = "macos")] RunEvent::Opened { urls } => { + use urlencoding::decode; + add_log(format!("📂 Tauri file opened event: {:?}", urls)); let mut added_files = false; for url in urls { let url_str = url.as_str(); if url_str.starts_with("file://") { - let file_path = url_str.strip_prefix("file://").unwrap_or(url_str); + let encoded_path = url_str.strip_prefix("file://").unwrap_or(url_str); + + // Decode URL-encoded characters (%20 -> space, etc.) + let file_path = match decode(encoded_path) { + Ok(decoded) => decoded.into_owned(), + Err(e) => { + add_log(format!("⚠️ Failed to decode file path: {} - {}", encoded_path, e)); + encoded_path.to_string() // Fallback to encoded path + } + }; + add_log(format!("📂 Processing opened file: {}", file_path)); - add_opened_file(file_path.to_string()); + add_opened_file(file_path); added_files = true; } } diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index a7c3355d8..43546413c 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -1,74 +1,89 @@ { - "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", - "productName": "Stirling-PDF", - "version": "2.0.0", - "identifier": "stirling.pdf.dev", - "build": { - "frontendDist": "../dist", - "devUrl": "http://localhost:5173", - "beforeDevCommand": "npm run dev -- --mode desktop", - "beforeBuildCommand": "npm run build -- --mode desktop" - }, - "app": { - "windows": [ - { - "title": "Stirling-PDF", - "width": 1280, - "height": 800, - "resizable": true, - "fullscreen": false - } - ] - }, - "bundle": { - "active": true, - "targets": ["deb", "rpm", "dmg", "app", "msi"], - "icon": [ - "icons/icon.png", - "icons/icon.icns", - "icons/icon.ico", - "icons/16x16.png", - "icons/32x32.png", - "icons/64x64.png", - "icons/128x128.png", - "icons/192x192.png" - ], - "resources": [ - "libs/*.jar", - "runtime/jre/**/*" - ], - "fileAssociations": [ - { - "ext": ["pdf"], - "name": "PDF Document", - "description": "Open PDF files with Stirling-PDF", - "role": "Editor", - "mimeType": "application/pdf" - } - ], - "linux": { - "deb": { - "desktopTemplate": "stirling-pdf.desktop" - } + "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", + "productName": "Stirling-PDF", + "version": "2.1.3", + "identifier": "stirling.pdf.dev", + "build": { + "frontendDist": "../dist", + "devUrl": "http://localhost:5173", + "beforeDevCommand": "npm run dev -- --mode desktop", + "beforeBuildCommand": "npm run build -- --mode desktop" }, - "windows": { - "certificateThumbprint": null, - "digestAlgorithm": "sha256", - "timestampUrl": "http://timestamp.digicert.com" + "app": { + "windows": [ + { + "title": "Stirling-PDF", + "width": 1280, + "height": 800, + "resizable": true, + "fullscreen": false + } + ] }, - "macOS": { - "minimumSystemVersion": "10.15", - "signingIdentity": null, - "entitlements": null, - "providerShortName": null + "bundle": { + "active": true, + "targets": [ + "deb", + "rpm", + "dmg", + "app", + "msi" + ], + "icon": [ + "icons/icon.png", + "icons/icon.icns", + "icons/icon.ico", + "icons/16x16.png", + "icons/32x32.png", + "icons/64x64.png", + "icons/128x128.png", + "icons/192x192.png" + ], + "resources": [ + "libs/*.jar", + "runtime/jre/**/*" + ], + "fileAssociations": [ + { + "ext": [ + "pdf" + ], + "name": "PDF Document", + "description": "Open PDF files with Stirling-PDF", + "role": "Editor", + "mimeType": "application/pdf" + } + ], + "linux": { + "deb": { + "desktopTemplate": "stirling-pdf.desktop" + } + }, + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "http://timestamp.digicert.com" + }, + "macOS": { + "minimumSystemVersion": "10.15", + "signingIdentity": null, + "entitlements": null, + "providerShortName": null + } + }, + "plugins": { + "shell": { + "open": true + }, + "fs": { + "requireLiteralLeadingDot": false + }, + "deep-link": { + "desktop": { + "schemes": [ + "stirlingpdf" + ] + } + } } - }, - "plugins": { - "shell": { - "open": true - }, - "fs": { - "requireLiteralLeadingDot": false - } - } } diff --git a/frontend/src/core/components/AppProviders.tsx b/frontend/src/core/components/AppProviders.tsx index 0ad5bc0f0..f40bd7954 100644 --- a/frontend/src/core/components/AppProviders.tsx +++ b/frontend/src/core/components/AppProviders.tsx @@ -21,6 +21,7 @@ import ErrorBoundary from "@app/components/shared/ErrorBoundary"; import { useScarfTracking } from "@app/hooks/useScarfTracking"; import { useAppInitialization } from "@app/hooks/useAppInitialization"; import { useLogoAssets } from '@app/hooks/useLogoAssets'; +import AppConfigLoader from '@app/components/shared/AppConfigLoader'; // Component to initialize scarf tracking (must be inside AppConfigProvider) function ScarfTrackingInitializer() { @@ -82,6 +83,7 @@ export function AppProviders({ children, appConfigRetryOptions, appConfigProvide {...appConfigProviderProps} > + diff --git a/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx b/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx index 4af2a6236..bc19a673c 100644 --- a/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx +++ b/frontend/src/core/components/annotation/shared/TextInputWithFont.tsx @@ -1,6 +1,5 @@ import React, { useState, useEffect } from 'react'; -import { Stack, TextInput, Select, Combobox, useCombobox, Group, Box, SegmentedControl } from '@mantine/core'; -import { useTranslation } from 'react-i18next'; +import { Stack, TextInput, Select, Combobox, useCombobox, Group, Box } from '@mantine/core'; import { ColorPicker } from '@app/components/annotation/shared/ColorPicker'; interface TextInputWithFontProps { @@ -15,8 +14,12 @@ interface TextInputWithFontProps { textAlign?: 'left' | 'center' | 'right'; onTextAlignChange?: (align: 'left' | 'center' | 'right') => void; disabled?: boolean; - label?: string; - placeholder?: string; + label: string; + placeholder: string; + fontLabel: string; + fontSizeLabel: string; + fontSizePlaceholder: string; + colorLabel?: string; onAnyChange?: () => void; } @@ -34,9 +37,12 @@ export const TextInputWithFont: React.FC = ({ disabled = false, label, placeholder, + fontLabel, + fontSizeLabel, + fontSizePlaceholder, + colorLabel, onAnyChange }) => { - const { t } = useTranslation(); const [fontSizeInput, setFontSizeInput] = useState(fontSize.toString()); const fontSizeCombobox = useCombobox(); const [isColorPickerOpen, setIsColorPickerOpen] = useState(false); @@ -70,8 +76,8 @@ export const TextInputWithFont: React.FC = ({ return ( { onTextChange(e.target.value); @@ -83,7 +89,7 @@ export const TextInputWithFont: React.FC = ({ {/* Font Selection */}